From 7a2bec6c4db38ba14b93f0a138fe3a1bb23ea57b Mon Sep 17 00:00:00 2001 From: Panos Sakkos Date: Tue, 1 Oct 2024 18:28:22 +0300 Subject: [PATCH 01/13] Merge changes from main to release (#1372) Clean up GitHub rule Co-authored-by: Ariel Ropek <79653153+arielkr256@users.noreply.github.com> Co-authored-by: Jack Naglieri --- rules/github_rules/github_action_failed.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rules/github_rules/github_action_failed.py b/rules/github_rules/github_action_failed.py index 64178ef6f..5911fbeda 100644 --- a/rules/github_rules/github_action_failed.py +++ b/rules/github_rules/github_action_failed.py @@ -2,7 +2,7 @@ from unittest.mock import MagicMock from global_filter_github import filter_include_event -from panther_base_helpers import deep_get, github_alert_context +from panther_base_helpers import github_alert_context # The keys for MONITORED_ACTIONS are gh_org/repo_name # The values for MONITORED_ACTIONS are a list of ["action_names"] @@ -15,22 +15,22 @@ def rule(event): global MONITORED_ACTIONS # pylint: disable=global-statement if isinstance(MONITORED_ACTIONS, MagicMock): MONITORED_ACTIONS = json.loads(MONITORED_ACTIONS()) # pylint: disable=not-callable - repo = deep_get(event, "repo", default="") - action_name = deep_get(event, "name", default="") + repo = event.get("repo", "") + action_name = event.get("name", "") return all( [ - deep_get(event, "action", default="") == "workflows.completed_workflow_run", + event.get("action", "") == "workflows.completed_workflow_run", + event.get("conclusion", "") == "failure", repo in MONITORED_ACTIONS, action_name in MONITORED_ACTIONS.get(repo, []), - deep_get(event, "conclusion", default="") == "failure", ] ) def title(event): - repo = deep_get(event, "repo", default="") - action_name = deep_get(event, "name", default="") - return f"The GitHub Action [{action_name}] in [{repo}] has failed" + repo = event.get("repo", "") + action_name = event.get("name", "") + return f"GitHub Action [{action_name}] in [{repo}] has failed" def alert_context(event): From 38d0ce8383c942a86c070252c5ac1c44b540b678 Mon Sep 17 00:00:00 2001 From: Panos Sakkos Date: Wed, 2 Oct 2024 17:25:46 +0300 Subject: [PATCH 02/13] Format Sublime YAML files (#1373) --- .../sublime_mailboxes_deactivated.yml | 114 +++++++++--------- .../sublime_rules/sublime_message_flagged.yml | 101 ++++++++-------- ..._message_source_deleted_or_deactivated.yml | 114 +++++++++--------- .../sublime_rules_deleted_or_deactivated.yml | 114 +++++++++--------- 4 files changed, 231 insertions(+), 212 deletions(-) diff --git a/rules/sublime_rules/sublime_mailboxes_deactivated.yml b/rules/sublime_rules/sublime_mailboxes_deactivated.yml index fb797d549..06a16bab2 100644 --- a/rules/sublime_rules/sublime_mailboxes_deactivated.yml +++ b/rules/sublime_rules/sublime_mailboxes_deactivated.yml @@ -21,64 +21,70 @@ Tests: Log: { "created_at": "2024-09-09 19:33:34.237078000", - "created_by": { - "active": true, - "created_at": "2024-08-28 22:05:15.715644000", - "email_address": "john.doe@sublime.security", - "first_name": "John", - "google_oauth_user_id": "", - "id": "cd3aedfe-a61f-4e0e-ba30-14dcc7883316", - "is_enrolled": true, - "last_name": "Doe", - "microsoft_oauth_user_id": "", - "role": "admin", - "updated_at": "2024-08-28 22:05:15.715644000" + "created_by": + { + "active": true, + "created_at": "2024-08-28 22:05:15.715644000", + "email_address": "john.doe@sublime.security", + "first_name": "John", + "google_oauth_user_id": "", + "id": "cd3aedfe-a61f-4e0e-ba30-14dcc7883316", + "is_enrolled": true, + "last_name": "Doe", + "microsoft_oauth_user_id": "", + "role": "admin", + "updated_at": "2024-08-28 22:05:15.715644000", }, - "data": { - "request": { - "authentication_method": "user_session", - "body": "{\"mailbox_ids\":[\"493c6e21-7787-419b-bada-7c4f50cbb932\"]}", - "id": "73444211-31af-42d8-99b4-34a139cf7d4a", - "ip": "1.2.3.4", - "method": "POST", - "path": "/v1/message-sources/febb5bf4-2ead-47b1-b467-0ac729bf6871/deactivate", - "query": { }, - "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" - } - }, - "id": "084732e5-7704-4bbe-ab5a-77f1aa65a737", - "type": "message_source.deactivate" - } + "data": + { + "request": + { + "authentication_method": "user_session", + "body": '{"mailbox_ids":["493c6e21-7787-419b-bada-7c4f50cbb932"]}', + "id": "73444211-31af-42d8-99b4-34a139cf7d4a", + "ip": "1.2.3.4", + "method": "POST", + "path": "/v1/message-sources/febb5bf4-2ead-47b1-b467-0ac729bf6871/deactivate", + "query": {}, + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", + }, + }, + "id": "084732e5-7704-4bbe-ab5a-77f1aa65a737", + "type": "message_source.deactivate", + } - ExpectedResult: true Name: Mailbox Deactivated Log: { "created_at": "2024-09-09 19:33:34.237078000", - "created_by": { - "active": true, - "created_at": "2024-08-28 22:05:15.715644000", - "email_address": "john.doe@sublime.security", - "first_name": "John", - "google_oauth_user_id": "", - "id": "cd3aedfe-a61f-4e0e-ba30-14dcc7883316", - "is_enrolled": true, - "last_name": "Doe", - "microsoft_oauth_user_id": "", - "role": "admin", - "updated_at": "2024-08-28 22:05:15.715644000" + "created_by": + { + "active": true, + "created_at": "2024-08-28 22:05:15.715644000", + "email_address": "john.doe@sublime.security", + "first_name": "John", + "google_oauth_user_id": "", + "id": "cd3aedfe-a61f-4e0e-ba30-14dcc7883316", + "is_enrolled": true, + "last_name": "Doe", + "microsoft_oauth_user_id": "", + "role": "admin", + "updated_at": "2024-08-28 22:05:15.715644000", + }, + "data": + { + "request": + { + "authentication_method": "user_session", + "body": '{"mailbox_ids":["493c6e21-7787-419b-bada-7c4f50cbb932"]}', + "id": "73444211-31af-42d8-99b4-34a139cf7d4a", + "ip": "1.2.3.4", + "method": "POST", + "path": "/v1/message-sources/febb5bf4-2ead-47b1-b467-0ac729bf6871/deactivate", + "query": {}, + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", + }, }, - "data": { - "request": { - "authentication_method": "user_session", - "body": "{\"mailbox_ids\":[\"493c6e21-7787-419b-bada-7c4f50cbb932\"]}", - "id": "73444211-31af-42d8-99b4-34a139cf7d4a", - "ip": "1.2.3.4", - "method": "POST", - "path": "/v1/message-sources/febb5bf4-2ead-47b1-b467-0ac729bf6871/deactivate", - "query": { }, - "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" - } - }, - "id": "084732e5-7704-4bbe-ab5a-77f1aa65a737", - "type": "message_source.deactivate_mailboxes" - } + "id": "084732e5-7704-4bbe-ab5a-77f1aa65a737", + "type": "message_source.deactivate_mailboxes", + } diff --git a/rules/sublime_rules/sublime_message_flagged.yml b/rules/sublime_rules/sublime_message_flagged.yml index a0fb9f6fc..ed7204686 100644 --- a/rules/sublime_rules/sublime_message_flagged.yml +++ b/rules/sublime_rules/sublime_message_flagged.yml @@ -16,54 +16,55 @@ Tests: Name: Message Flagged Log: { - "p_source_file": { - "aws_s3_bucket": "audit.log.export", - "aws_s3_key": "sublime_platform_message_events/2024/09/24/164544Z-FPXIFG.json" - }, - "p_any_sha256_hashes": [ - "fb8b46e3317ac7d5036c6b21517d363634293c6d4f6bf1b1e67548c80948a1c6" - ], - "p_event_time": "2024-09-24 16:45:43.302769000", - "p_log_type": "Sublime.MessageEvent", - "p_parse_time": "2024-09-24 16:51:47.687095351", - "p_row_id": "a23385494d57dfbbbdcbe4fa218101", - "p_schema_version": 0, - "p_source_id": "7e2a59aa-687e-430e-ae4a-81d3c0163f52", - "p_source_label": "Sublime Real Logs", - "p_udm": {}, - "created_at": "2024-09-24 16:45:43.302769000", - "data": { - "flagged_rules": [ - { - "id": "b0ab266f-8a12-4020-b165-e97bb1aacc42", - "name": "Credential phishing: Engaging language and other indicators (untrusted sender)" - }, - { - "id": "a014f82e-f2d7-4058-adb1-36fc086de0b8", - "name": "Attachment: HTML smuggling with unescape" - }, - { - "id": "e4866908-60fe-46f0-866e-84d412627006", - "name": "Headers: Zimbra mailer from a non-supported OS version" - }, - { - "id": "5a9dc2cd-39f5-4814-95df-aa7614cc8bdd", - "name": "Impersonation: Human Resources with link or attachment and engaging language" - }, - { - "id": "7988f1f5-5c95-42c2-9140-ead5a975918e", - "name": "Request for Quote or Purchase (RFQ|RFP) with HTML smuggling attachment" - } - ], - "message": { - "canonical_id": "fb8b46e3317ac7d5036c6b21517d363634293c6d4f6bf1b1e67548c80948a1c6", - "external_id": "b86b1e58-e9f8-4b55-8b54-1402f9f95e69", - "id": "019224ec-aba6-763d-bb2e-cd4cbd40a29f", - "mailbox": { - "id": "624c8394-4fe2-4ba0-bd2b-86d2e503c614" - }, - "message_source_id": "91956379-c2f3-4c50-a410-3ba89fb8bc74" - } - }, - "type": "message.flagged" + "p_source_file": + { + "aws_s3_bucket": "audit.log.export", + "aws_s3_key": "sublime_platform_message_events/2024/09/24/164544Z-FPXIFG.json", + }, + "p_any_sha256_hashes": + ["fb8b46e3317ac7d5036c6b21517d363634293c6d4f6bf1b1e67548c80948a1c6"], + "p_event_time": "2024-09-24 16:45:43.302769000", + "p_log_type": "Sublime.MessageEvent", + "p_parse_time": "2024-09-24 16:51:47.687095351", + "p_row_id": "a23385494d57dfbbbdcbe4fa218101", + "p_schema_version": 0, + "p_source_id": "7e2a59aa-687e-430e-ae4a-81d3c0163f52", + "p_source_label": "Sublime Real Logs", + "p_udm": {}, + "created_at": "2024-09-24 16:45:43.302769000", + "data": + { + "flagged_rules": + [ + { + "id": "b0ab266f-8a12-4020-b165-e97bb1aacc42", + "name": "Credential phishing: Engaging language and other indicators (untrusted sender)", + }, + { + "id": "a014f82e-f2d7-4058-adb1-36fc086de0b8", + "name": "Attachment: HTML smuggling with unescape", + }, + { + "id": "e4866908-60fe-46f0-866e-84d412627006", + "name": "Headers: Zimbra mailer from a non-supported OS version", + }, + { + "id": "5a9dc2cd-39f5-4814-95df-aa7614cc8bdd", + "name": "Impersonation: Human Resources with link or attachment and engaging language", + }, + { + "id": "7988f1f5-5c95-42c2-9140-ead5a975918e", + "name": "Request for Quote or Purchase (RFQ|RFP) with HTML smuggling attachment", + }, + ], + "message": + { + "canonical_id": "fb8b46e3317ac7d5036c6b21517d363634293c6d4f6bf1b1e67548c80948a1c6", + "external_id": "b86b1e58-e9f8-4b55-8b54-1402f9f95e69", + "id": "019224ec-aba6-763d-bb2e-cd4cbd40a29f", + "mailbox": { "id": "624c8394-4fe2-4ba0-bd2b-86d2e503c614" }, + "message_source_id": "91956379-c2f3-4c50-a410-3ba89fb8bc74", + }, + }, + "type": "message.flagged", } diff --git a/rules/sublime_rules/sublime_message_source_deleted_or_deactivated.yml b/rules/sublime_rules/sublime_message_source_deleted_or_deactivated.yml index 15855307a..e4c211501 100644 --- a/rules/sublime_rules/sublime_message_source_deleted_or_deactivated.yml +++ b/rules/sublime_rules/sublime_message_source_deleted_or_deactivated.yml @@ -21,64 +21,70 @@ Tests: Log: { "created_at": "2024-09-09 19:33:34.237078000", - "created_by": { - "active": true, - "created_at": "2024-08-28 22:05:15.715644000", - "email_address": "john.doe@sublime.security", - "first_name": "John", - "google_oauth_user_id": "", - "id": "cd3aedfe-a61f-4e0e-ba30-14dcc7883316", - "is_enrolled": true, - "last_name": "Doe", - "microsoft_oauth_user_id": "", - "role": "admin", - "updated_at": "2024-08-28 22:05:15.715644000" + "created_by": + { + "active": true, + "created_at": "2024-08-28 22:05:15.715644000", + "email_address": "john.doe@sublime.security", + "first_name": "John", + "google_oauth_user_id": "", + "id": "cd3aedfe-a61f-4e0e-ba30-14dcc7883316", + "is_enrolled": true, + "last_name": "Doe", + "microsoft_oauth_user_id": "", + "role": "admin", + "updated_at": "2024-08-28 22:05:15.715644000", }, - "data": { - "request": { - "authentication_method": "user_session", - "body": "{\"mailbox_ids\":[\"493c6e21-7787-419b-bada-7c4f50cbb932\"]}", - "id": "73444211-31af-42d8-99b4-34a139cf7d4a", - "ip": "1.2.3.4", - "method": "POST", - "path": "/v1/message-sources/febb5bf4-2ead-47b1-b467-0ac729bf6871/deactivate", - "query": { }, - "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" - } - }, - "id": "084732e5-7704-4bbe-ab5a-77f1aa65a737", - "type": "message_source.deactivate" - } + "data": + { + "request": + { + "authentication_method": "user_session", + "body": '{"mailbox_ids":["493c6e21-7787-419b-bada-7c4f50cbb932"]}', + "id": "73444211-31af-42d8-99b4-34a139cf7d4a", + "ip": "1.2.3.4", + "method": "POST", + "path": "/v1/message-sources/febb5bf4-2ead-47b1-b467-0ac729bf6871/deactivate", + "query": {}, + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", + }, + }, + "id": "084732e5-7704-4bbe-ab5a-77f1aa65a737", + "type": "message_source.deactivate", + } - ExpectedResult: false Name: Other Events Log: { "created_at": "2024-09-09 19:33:34.237078000", - "created_by": { - "active": true, - "created_at": "2024-08-28 22:05:15.715644000", - "email_address": "john.doe@sublime.security", - "first_name": "John", - "google_oauth_user_id": "", - "id": "cd3aedfe-a61f-4e0e-ba30-14dcc7883316", - "is_enrolled": true, - "last_name": "Doe", - "microsoft_oauth_user_id": "", - "role": "admin", - "updated_at": "2024-08-28 22:05:15.715644000" + "created_by": + { + "active": true, + "created_at": "2024-08-28 22:05:15.715644000", + "email_address": "john.doe@sublime.security", + "first_name": "John", + "google_oauth_user_id": "", + "id": "cd3aedfe-a61f-4e0e-ba30-14dcc7883316", + "is_enrolled": true, + "last_name": "Doe", + "microsoft_oauth_user_id": "", + "role": "admin", + "updated_at": "2024-08-28 22:05:15.715644000", + }, + "data": + { + "request": + { + "authentication_method": "user_session", + "body": '{"mailbox_ids":["493c6e21-7787-419b-bada-7c4f50cbb932"]}', + "id": "73444211-31af-42d8-99b4-34a139cf7d4a", + "ip": "1.2.3.4", + "method": "POST", + "path": "/v1/message-sources/febb5bf4-2ead-47b1-b467-0ac729bf6871/deactivate", + "query": {}, + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", + }, }, - "data": { - "request": { - "authentication_method": "user_session", - "body": "{\"mailbox_ids\":[\"493c6e21-7787-419b-bada-7c4f50cbb932\"]}", - "id": "73444211-31af-42d8-99b4-34a139cf7d4a", - "ip": "1.2.3.4", - "method": "POST", - "path": "/v1/message-sources/febb5bf4-2ead-47b1-b467-0ac729bf6871/deactivate", - "query": { }, - "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" - } - }, - "id": "084732e5-7704-4bbe-ab5a-77f1aa65a737", - "type": "rule.deactivate" - } + "id": "084732e5-7704-4bbe-ab5a-77f1aa65a737", + "type": "rule.deactivate", + } diff --git a/rules/sublime_rules/sublime_rules_deleted_or_deactivated.yml b/rules/sublime_rules/sublime_rules_deleted_or_deactivated.yml index 48bcdd7d4..9a823a79d 100644 --- a/rules/sublime_rules/sublime_rules_deleted_or_deactivated.yml +++ b/rules/sublime_rules/sublime_rules_deleted_or_deactivated.yml @@ -21,64 +21,70 @@ Tests: Log: { "created_at": "2024-09-09 19:33:34.237078000", - "created_by": { - "active": true, - "created_at": "2024-08-28 22:05:15.715644000", - "email_address": "john.doe@sublime.security", - "first_name": "John", - "google_oauth_user_id": "", - "id": "cd3aedfe-a61f-4e0e-ba30-14dcc7883316", - "is_enrolled": true, - "last_name": "Doe", - "microsoft_oauth_user_id": "", - "role": "admin", - "updated_at": "2024-08-28 22:05:15.715644000" + "created_by": + { + "active": true, + "created_at": "2024-08-28 22:05:15.715644000", + "email_address": "john.doe@sublime.security", + "first_name": "John", + "google_oauth_user_id": "", + "id": "cd3aedfe-a61f-4e0e-ba30-14dcc7883316", + "is_enrolled": true, + "last_name": "Doe", + "microsoft_oauth_user_id": "", + "role": "admin", + "updated_at": "2024-08-28 22:05:15.715644000", }, - "data": { - "request": { - "authentication_method": "user_session", - "body": "{\"mailbox_ids\":[\"493c6e21-7787-419b-bada-7c4f50cbb932\"]}", - "id": "73444211-31af-42d8-99b4-34a139cf7d4a", - "ip": "1.2.3.4", - "method": "POST", - "path": "/v1/message-sources/febb5bf4-2ead-47b1-b467-0ac729bf6871/deactivate", - "query": { }, - "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" - } - }, - "id": "084732e5-7704-4bbe-ab5a-77f1aa65a737", - "type": "rules.delete" - } + "data": + { + "request": + { + "authentication_method": "user_session", + "body": '{"mailbox_ids":["493c6e21-7787-419b-bada-7c4f50cbb932"]}', + "id": "73444211-31af-42d8-99b4-34a139cf7d4a", + "ip": "1.2.3.4", + "method": "POST", + "path": "/v1/message-sources/febb5bf4-2ead-47b1-b467-0ac729bf6871/deactivate", + "query": {}, + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", + }, + }, + "id": "084732e5-7704-4bbe-ab5a-77f1aa65a737", + "type": "rules.delete", + } - ExpectedResult: false Name: Other Events Log: { "created_at": "2024-09-09 19:33:34.237078000", - "created_by": { - "active": true, - "created_at": "2024-08-28 22:05:15.715644000", - "email_address": "john.doe@sublime.security", - "first_name": "John", - "google_oauth_user_id": "", - "id": "cd3aedfe-a61f-4e0e-ba30-14dcc7883316", - "is_enrolled": true, - "last_name": "Doe", - "microsoft_oauth_user_id": "", - "role": "admin", - "updated_at": "2024-08-28 22:05:15.715644000" + "created_by": + { + "active": true, + "created_at": "2024-08-28 22:05:15.715644000", + "email_address": "john.doe@sublime.security", + "first_name": "John", + "google_oauth_user_id": "", + "id": "cd3aedfe-a61f-4e0e-ba30-14dcc7883316", + "is_enrolled": true, + "last_name": "Doe", + "microsoft_oauth_user_id": "", + "role": "admin", + "updated_at": "2024-08-28 22:05:15.715644000", + }, + "data": + { + "request": + { + "authentication_method": "user_session", + "body": '{"mailbox_ids":["493c6e21-7787-419b-bada-7c4f50cbb932"]}', + "id": "73444211-31af-42d8-99b4-34a139cf7d4a", + "ip": "1.2.3.4", + "method": "POST", + "path": "/v1/message-sources/febb5bf4-2ead-47b1-b467-0ac729bf6871/deactivate", + "query": {}, + "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", + }, }, - "data": { - "request": { - "authentication_method": "user_session", - "body": "{\"mailbox_ids\":[\"493c6e21-7787-419b-bada-7c4f50cbb932\"]}", - "id": "73444211-31af-42d8-99b4-34a139cf7d4a", - "ip": "1.2.3.4", - "method": "POST", - "path": "/v1/message-sources/febb5bf4-2ead-47b1-b467-0ac729bf6871/deactivate", - "query": { }, - "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" - } - }, - "id": "084732e5-7704-4bbe-ab5a-77f1aa65a737", - "type": "message_source.deactivate" - } + "id": "084732e5-7704-4bbe-ab5a-77f1aa65a737", + "type": "message_source.deactivate", + } From b33b6c0a0d7e108494fce2ad030336c5a9302425 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Oct 2024 14:27:36 +0000 Subject: [PATCH 03/13] build(deps): bump docker/setup-buildx-action from 3.6.1 to 3.7.0 (#1375) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5d8e0b94c..fa5ecca2d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -31,7 +31,7 @@ jobs: - uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf #v3.2.0 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db #v3.6.1 + uses: docker/setup-buildx-action@8026d2bc3645ea78b0d2544766a1225eb5691f89 #v3.7.0 - name: Build Image run: docker buildx build --load -f Dockerfile -t panther-analysis:latest . - name: Test Image From 4d9525717256677ab1677610f24be5fd7a9a7cda Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 13:32:10 +0300 Subject: [PATCH 04/13] build(deps): bump docker/setup-buildx-action from 3.7.0 to 3.7.1 (#1377) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index fa5ecca2d..21ee22919 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -31,7 +31,7 @@ jobs: - uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf #v3.2.0 - name: Set up Docker Buildx id: buildx - uses: docker/setup-buildx-action@8026d2bc3645ea78b0d2544766a1225eb5691f89 #v3.7.0 + uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 #v3.7.1 - name: Build Image run: docker buildx build --load -f Dockerfile -t panther-analysis:latest . - name: Test Image From a61a96f477e0cca6253e1d7611800e33e5de7d8f Mon Sep 17 00:00:00 2001 From: ben-githubs <38414634+ben-githubs@users.noreply.github.com> Date: Mon, 7 Oct 2024 10:16:59 -0500 Subject: [PATCH 05/13] Remove deprecated rules (#1369) Co-authored-by: Ariel Ropek <79653153+arielkr256@users.noreply.github.com> --- .github/workflows/check-deprecated.yml | 42 ++ .scripts/deleted_rules.py | 105 ++++ Makefile | 6 + ...otentially_compromised_service_role_cr.yml | 55 -- deprecated.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 -- 72 files changed, 187 insertions(+), 6337 deletions(-) create mode 100644 .github/workflows/check-deprecated.yml create mode 100644 .scripts/deleted_rules.py delete mode 100644 correlation_rules/aws_potentially_compromised_service_role_cr.yml create mode 100644 deprecated.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/.github/workflows/check-deprecated.yml b/.github/workflows/check-deprecated.yml new file mode 100644 index 000000000..c34c2555e --- /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 Release + 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 diff --git a/.scripts/deleted_rules.py b/.scripts/deleted_rules.py new file mode 100644 index 000000000..0edabe635 --- /dev/null +++ b/.scripts/deleted_rules.py @@ -0,0 +1,105 @@ +""" 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 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.]+)"?') + + +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): + 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!") + 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()) + + 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(): + 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..37f486500 100644 --- a/Makefile +++ b/Makefile @@ -62,6 +62,12 @@ install: test: global-helpers-unit-test pipenv run panther_analysis_tool test $(TEST_ARGS) +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 . 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/deprecated.txt b/deprecated.txt new file mode 100644 index 000000000..95642d3a0 --- /dev/null +++ b/deprecated.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 28e4fcb96..4415e4da9 100644 --- a/packs/aws.yml +++ b/packs/aws.yml @@ -164,7 +164,6 @@ PackDefinition: - VPC.DNS.Tunneling - VPCFlow.Port.Scanning # Correlation Rules - - AWS.Potentially.Stolen.Service.Role - AWS.Privilege.Escalation.Via.User.Compromise - AWS.SSO.Access.Token.Retrieved.by.Unauthenticated.IP - AWS.User.Takeover.Via.Password.Reset 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 039ad9aa16ed101a7709a8b307e6119be84bc6ca Mon Sep 17 00:00:00 2001 From: akozlovets098 <95437895+akozlovets098@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:35:59 +0300 Subject: [PATCH 06/13] THREAT-397 Reformat deep_get(event to event.deep_get( (#1374) Co-authored-by: Ben Airey Co-authored-by: Ariel --- .scripts/mitre_mapping_check.py | 33 +++--- global_helpers/gcp_base_helpers.py | 23 ++-- global_helpers/global_filter_auth0.py | 5 +- global_helpers/global_filter_azuresignin.py | 5 +- global_helpers/global_filter_cloudflare.py | 3 - global_helpers/global_filter_github.py | 5 +- global_helpers/global_filter_notion.py | 5 +- global_helpers/global_filter_snyk.py | 5 +- global_helpers/global_filter_tailscale.py | 5 +- global_helpers/global_filter_tines.py | 5 +- global_helpers/global_helpers_test.py | 103 +++++++++--------- global_helpers/panther_asana_helpers.py | 21 ++-- global_helpers/panther_auth0_helpers.py | 11 +- global_helpers/panther_azuresignin_helpers.py | 21 ++-- global_helpers/panther_base_helpers.py | 32 +++--- global_helpers/panther_greynoise_helpers.py | 5 +- global_helpers/panther_ipinfo_helpers.py | 6 +- global_helpers/panther_lookuptable_helpers.py | 2 +- global_helpers/panther_notion_helpers.py | 7 +- global_helpers/panther_sublime_helpers.py | 2 +- global_helpers/panther_tailscale_helpers.py | 9 +- ...ation_from_crowdstrike_unmanaged_device.py | 4 +- ...login_from_crowdstrike_unmanaged_device.py | 5 +- ...login_from_crowdstrike_unmanaged_device.py | 5 +- .../appomni_alert_passthrough.py | 2 +- .../asana_service_account_created.py | 7 +- .../asana_rules/asana_team_privacy_public.py | 9 +- ...orkspace_default_session_duration_never.py | 9 +- .../asana_workspace_email_domain_added.py | 9 +- ...ace_form_link_auth_requirement_disabled.py | 7 +- ...rkspace_guest_invite_permissions_anyone.py | 9 +- .../asana_rules/asana_workspace_new_admin.py | 7 +- .../asana_rules/asana_workspace_org_export.py | 7 +- ..._workspace_password_requirements_simple.py | 11 +- ...orkspace_require_app_approvals_disabled.py | 9 +- .../asana_workspace_saml_optional.py | 9 +- .../atlassian_rules/user_logged_in_as_user.py | 15 +-- .../auth0_cic_credential_stuffing.py | 2 +- .../auth0_rules/auth0_custom_role_created.py | 25 ++--- .../auth0_integration_installed.py | 13 +-- .../auth0_mfa_factor_setting_enabled.py | 13 +-- .../auth0_rules/auth0_mfa_policy_disabled.py | 15 ++- rules/auth0_rules/auth0_mfa_policy_enabled.py | 13 +-- .../auth0_mfa_risk_assessment_disabled.py | 17 ++- .../auth0_mfa_risk_assessment_enabled.py | 17 ++- .../auth0_post_login_action_flow.py | 18 +-- .../auth0_user_invitation_created.py | 13 +-- rules/auth0_rules/auth0_user_joined_tenant.py | 14 +-- .../aws_ami_modified_for_public_access.py | 6 +- .../aws_cloudtrail_account_discovery.py | 4 +- .../aws_cloudtrail_created.py | 4 +- ...loudtrail_loginprofilecreatedormodified.py | 10 +- ...ws_cloudtrail_password_policy_discovery.py | 4 +- .../aws_cloudtrail_stopped.py | 4 +- ...aws_cloudtrail_unsuccessful_mfa_attempt.py | 10 +- .../aws_cloudtrail_useraccesskeyauth.py | 2 +- .../aws_codebuild_made_public.py | 8 +- .../aws_console_login_without_mfa.py | 22 ++-- .../aws_console_login_without_saml.py | 4 +- .../aws_console_root_login.py | 11 +- .../aws_console_root_login_failed.py | 6 +- .../aws_ec2_manual_security_group_changes.py | 10 +- .../aws_ec2_monitoring.py | 10 +- .../aws_ec2_startup_script_change.py | 14 +-- .../aws_ec2_traffic_mirroring.py | 4 +- rules/aws_cloudtrail_rules/aws_ecr_crud.py | 8 +- rules/aws_cloudtrail_rules/aws_ecr_events.py | 4 +- .../aws_iam_assume_role_blocklist_ignored.py | 6 +- .../aws_iam_compromised_key_quarantine.py | 2 +- ...m_entity_created_without_cloudformation.py | 8 +- .../aws_iam_user_key_created.py | 12 +- .../aws_iam_user_recon_denied.py | 10 +- .../aws_key_compromised.py | 6 +- rules/aws_cloudtrail_rules/aws_lambda_crud.py | 8 +- .../aws_cloudtrail_rules/aws_macie_evasion.py | 4 +- ...aws_modify_cloud_compute_infrastructure.py | 16 ++- .../aws_network_acl_permissive_entry.py | 8 +- .../aws_rds_master_pass_updated.py | 9 +- .../aws_rds_publicrestore.py | 4 +- .../aws_rds_snapshot_shared.py | 2 +- .../aws_resource_made_public.py | 3 +- .../aws_root_access_key_created.py | 4 +- .../aws_cloudtrail_rules/aws_root_activity.py | 11 +- .../aws_root_password_changed.py | 6 +- .../aws_s3_bucket_deleted.py | 4 +- .../aws_s3_bucket_policy_modified.py | 4 +- .../aws_cloudtrail_rules/aws_saml_activity.py | 6 +- .../aws_security_configuration_change.py | 11 +- .../aws_software_discovery.py | 6 +- .../aws_unauthorized_api_call.py | 6 +- .../aws_cloudtrail_rules/aws_unused_region.py | 4 +- .../aws_update_credentials.py | 6 +- .../aws_user_login_profile_modified.py | 12 +- .../aws_waf_disassociation.py | 9 +- .../system_namespace_public_ip.py | 4 +- .../aws_guardduty_high_sev_findings.py | 4 +- .../aws_guardduty_low_sev_findings.py | 4 +- .../aws_guardduty_med_sev_findings.py | 4 +- .../azure_failed_signins.py | 3 +- rules/azure_signin_rules/azure_legacyauth.py | 7 +- .../azure_risklevel_passthrough.py | 7 +- rules/box_rules/box_access_granted.py | 5 +- rules/box_rules/box_anomalous_download.py | 2 +- .../box_event_triggered_externally.py | 3 +- rules/box_rules/box_item_shared_externally.py | 12 +- rules/box_rules/box_malicious_content.py | 4 +- rules/box_rules/box_new_login.py | 5 +- rules/box_rules/box_policy_violation.py | 4 +- rules/box_rules/box_untrusted_device.py | 5 +- rules/box_rules/box_user_downloads.py | 5 +- .../box_rules/box_user_permission_updates.py | 4 +- .../crowdstrike_macos_add_trusted_cert.py | 4 +- ...owdstrike_macos_osascript_administrator.py | 4 +- .../crowdstrike_macos_plutil_usage.py | 4 +- .../dropbox_admin_sign_in_as_session.py | 9 +- rules/dropbox_rules/dropbox_external_share.py | 5 +- .../dropbox_linked_team_application_added.py | 23 ++-- .../dropbox_ownership_transfer.py | 13 +-- .../dropbox_user_disabled_2fa.py | 11 +- rules/duo_rules/duo_user_action_fraudulent.py | 15 +-- rules/duo_rules/duo_user_anomalous_push.py | 15 +-- rules/duo_rules/duo_user_bypass_code_used.py | 15 +-- .../duo_user_endpoint_failure_multi.py | 15 +-- ...attempts_violating_vpc_service_controls.py | 17 ++- .../gcp_bigquery_large_scan.py | 30 ++--- .../gcp_cloud_run_service_created.py | 16 ++- .../gcp_cloud_run_set_iam_policy.py | 20 ++-- ...oud_storage_buckets_modified_or_deleted.py | 16 ++- ...oudbuild_potential_privilege_escalation.py | 13 +-- .../gcp_cloudfunctions_functions_create.py | 11 +- .../gcp_cloudfunctions_functions_update.py | 11 +- ...teinstances_create_privilege_escalation.py | 17 ++- .../gcp_destructive_queries.py | 39 +++---- .../gcp_dns_zone_modified_or_deleted.py | 9 +- .../gcp_firewall_rule_created.py | 13 +-- .../gcp_firewall_rule_deleted.py | 13 +-- .../gcp_firewall_rule_modified.py | 9 +- rules/gcp_audit_rules/gcp_gcs_iam_changes.py | 9 +- rules/gcp_audit_rules/gcp_gcs_public.py | 6 +- rules/gcp_audit_rules/gcp_iam_corp_email.py | 6 +- .../gcp_iam_custom_role_changes.py | 8 +- .../gcp_iam_org_folder_changes.py | 15 +-- ...p_iam_roles_update_privilege_escalation.py | 11 +- .../gcp_iam_service_account_key_create.py | 11 +- ...s_get_access_token_privilege_escalation.py | 5 +- .../gcp_iam_service_accounts_sign_blob.py | 9 +- .../gcp_iam_serviceaccounts_signjwt.py | 17 ++- .../gcp_log_bucket_or_sink_deleted.py | 12 +- .../gcp_logging_settings_modified.py | 20 ++-- .../gcp_logging_sink_modified.py | 10 +- ...to_create_or_manage_service_account_key.py | 26 ++--- ...vilege_escalation_by_deployments_create.py | 11 +- .../gcp_service_account_access_denied.py | 7 +- .../gcp_service_account_or_keys_created.py | 21 ++-- ...age_apikeys_create_privilege_escalation.py | 11 +- .../gcp_audit_rules/gcp_sql_config_changes.py | 7 +- rules/gcp_audit_rules/gcp_unused_regions.py | 4 +- ...gcp_user_added_to_iap_protected_service.py | 13 +-- .../gcp_vpc_flow_logs_disabled.py | 13 +-- .../gcp_workforce_pool_created_or_updated.py | 4 +- ..._attempts_violating_iap_access_controls.py | 15 +-- .../gcp_k8s_cron_job_created_or_modified.py | 11 +- rules/gcp_k8s_rules/gcp_k8s_exec_into_pod.py | 4 +- rules/gcp_k8s_rules/gcp_k8s_ioc_activity.py | 15 ++- .../gcp_k8s_new_daemonset_deployed.py | 11 +- ...p_k8s_pod_attached_to_node_host_network.py | 11 +- ...od_create_or_modify_host_path_vol_mount.py | 27 +++-- .../gcp_k8s_pod_using_host_pid_namespace.py | 13 +-- .../gcp_k8s_privileged_pod_created.py | 24 ++-- ...gcp_k8s_service_type_node_port_deployed.py | 17 ++- rules/github_rules/github_webhook_modified.py | 6 +- ...oduction_password_reset_multiple_emails.py | 4 +- ...e_workspace_advanced_protection_program.py | 5 +- ...le_workspace_apps_marketplace_allowlist.py | 15 +-- .../gsuite_advanced_protection.py | 7 +- .../gsuite_calendar_made_public.py | 9 +- .../gsuite_doc_ownership_transfer.py | 5 +- .../gsuite_external_forwarding.py | 11 +- .../gsuite_google_access.py | 5 +- .../gsuite_gov_attack.py | 7 +- .../gsuite_group_banned_user.py | 7 +- .../gsuite_leaked_password.py | 6 +- .../gsuite_login_type.py | 8 +- .../gsuite_mobile_device_compromise.py | 9 +- ...gsuite_mobile_device_screen_unlock_fail.py | 8 +- ...suite_mobile_device_suspicious_activity.py | 7 +- .../gsuite_passthrough_rule.py | 15 +-- .../gsuite_suspicious_logins.py | 6 +- .../gsuite_two_step_verification.py | 7 +- .../gsuite_user_suspended.py | 6 +- ...ite_workspace_calendar_external_sharing.py | 13 +-- .../gsuite_workspace_data_export_created.py | 5 +- ...te_workspace_gmail_default_routing_rule.py | 7 +- ...ace_gmail_enhanced_predelivery_scanning.py | 15 +-- ...rkspace_gmail_security_sandbox_disabled.py | 15 +-- ...kspace_password_enforce_strong_disabled.py | 11 +- ...gsuite_workspace_password_reuse_enabled.py | 11 +- ...ite_workspace_trusted_domains_allowlist.py | 7 +- .../gsuite_drive_external_share.py | 8 +- .../gsuite_drive_overly_visible.py | 11 +- .../gsuite_drive_visibility_change.py | 9 +- rules/mongodb_rules/mongodb_2fa_disabled.py | 4 +- .../mongodb_access_allowed_from_anywhere.py | 10 +- .../mongodb_alerting_disabled.py | 8 +- .../mongodb_atlas_api_key_created.py | 13 +-- .../mongodb_external_user_invited.py | 5 +- ...mongodb_external_user_invited_no_config.py | 6 +- .../mongodb_identity_provider_activity.py | 2 +- .../mongodb_rules/mongodb_logging_toggled.py | 4 +- ...odb_org_membership_restriction_disabled.py | 4 +- .../mongodb_user_created_or_deleted.py | 2 +- .../mongodb_user_roles_changed.py | 2 +- .../netskope_unauthorized_api_calls.py | 5 +- ...ettings_enforce_saml_sso_config_updated.py | 7 +- ...orkspace_settings_public_homepage_added.py | 7 +- rules/okta_rules/okta_admin_role_assigned.py | 20 ++-- .../okta_rules/okta_anonymizing_vpn_login.py | 12 +- rules/okta_rules/okta_api_key_created.py | 6 +- rules/okta_rules/okta_api_key_revoked.py | 6 +- .../okta_app_unauthorized_access_attempt.py | 4 +- rules/okta_rules/okta_idp_create_modify.py | 10 +- rules/okta_rules/okta_idp_signin.py | 10 +- rules/okta_rules/okta_login_signal.py | 2 +- ...ta_new_behavior_accessing_admin_console.py | 22 ++-- .../okta_org2org_creation_modification.py | 12 +- rules/okta_rules/okta_password_accessed.py | 8 +- .../okta_password_extraction_via_scim.py | 14 +-- ...ta_phishing_attempt_blocked_by_fastpass.py | 10 +- .../okta_potentially_stolen_session.py | 30 ++--- rules/okta_rules/okta_support_reset.py | 10 +- ..._threatinsight_security_threat_detected.py | 4 +- .../onepassword_lut_sensitive_item_access.py | 12 +- .../onepassword_sensitive_item_access.py | 10 +- .../onepassword_unusual_client.py | 12 +- .../osquery_linux_aws_commands.py | 8 +- .../osquery_linux_logins_non_office.py | 8 +- .../osquery_mac_application_firewall.py | 6 +- .../osquery_mac_enable_auto_update.py | 9 +- ...osquery_mac_osx_attacks_keyboard_events.py | 6 +- rules/osquery_rules/osquery_outdated.py | 6 +- rules/osquery_rules/osquery_outdated_macos.py | 6 +- rules/osquery_rules/osquery_ssh_listener.py | 5 +- .../osquery_rules/osquery_suspicious_cron.py | 4 +- .../panther_detection_deleted.py | 6 +- .../panther_sensitive_role_created.py | 11 +- .../panther_sensitive_role_created.yml | 2 +- .../panther_user_modified.py | 11 +- .../sentinelone_alert_passthrough.py | 8 +- .../sentinelone_rules/sentinelone_threats.py | 10 +- .../slack_rules/slack_app_access_expanded.py | 16 +-- rules/slack_rules/slack_app_added.py | 14 +-- rules/slack_rules/slack_app_removed.py | 6 +- rules/slack_rules/slack_application_dos.py | 4 +- .../slack_legal_hold_policy_modified.py | 4 +- .../slack_privilege_changed_to_user.py | 6 +- .../slack_user_privilege_escalation.py | 10 +- rules/snyk_rules/snyk_misc_settings.py | 4 +- rules/snyk_rules/snyk_org_settings.py | 4 +- rules/snyk_rules/snyk_ou_change.py | 6 +- rules/snyk_rules/snyk_project_settings.py | 6 +- rules/snyk_rules/snyk_role_change.py | 8 +- rules/snyk_rules/snyk_svcacct_change.py | 6 +- .../snyk_rules/snyk_system_externalaccess.py | 4 +- rules/snyk_rules/snyk_system_policysetting.py | 2 +- rules/snyk_rules/snyk_system_sso.py | 2 +- rules/snyk_rules/snyk_user_mgmt.py | 8 +- .../standard_rules/impossible_travel_login.py | 6 +- .../tailscale_https_disabled.py | 11 +- ..._machine_approval_requirements_disabled.py | 9 +- .../tailscale_magicdns_disabled.py | 11 +- .../tines_actions_disabled_changes.py | 7 +- rules/tines_rules/tines_custom_ca.py | 11 +- .../tines_enqueued_retrying_job_deletion.py | 9 +- .../tines_global_resource_destruction.py | 12 +- rules/tines_rules/tines_sso_settings.py | 11 +- .../tines_story_items_destruction.py | 11 +- .../tines_rules/tines_story_jobs_clearance.py | 9 +- rules/tines_rules/tines_team_destruction.py | 9 +- rules/tines_rules/tines_tenant_authtoken.py | 17 ++- templates/example_rule.py | 4 +- 280 files changed, 1160 insertions(+), 1533 deletions(-) diff --git a/.scripts/mitre_mapping_check.py b/.scripts/mitre_mapping_check.py index d856ec7a7..0f5d0e91c 100644 --- a/.scripts/mitre_mapping_check.py +++ b/.scripts/mitre_mapping_check.py @@ -11,28 +11,26 @@ # All MITRE Tags must match this regex pattern MITRE_PATTERN = re.compile("^TA\d+\:T\d+(\.\d+)?$") + def main(path: Path) -> bool: # Load Repo analysis_items = load_analysis_specs([path], ignore_files=[]) - items_with_invalid_mappings = [] # Record all items with bad tags + items_with_invalid_mappings = [] # Record all items with bad tags for analysis_item in analysis_items: - rel_path = analysis_item[0] # Relative path to YAML file - spec = analysis_item[2] # YAML spec as a dict + rel_path = analysis_item[0] # Relative path to YAML file + spec = analysis_item[2] # YAML spec as a dict - bad_tags = [] # Record the invalid tags for this analysis item + bad_tags = [] # Record the invalid tags for this analysis item if reports := spec.get("Reports"): if mitre := reports.get("MITRE ATT&CK"): for mapping in mitre: if not MITRE_PATTERN.match(mapping): bad_tags.append(mapping) - + if bad_tags: - items_with_invalid_mappings.append({ - "rel_path": rel_path, - "bad_tags": bad_tags - }) - + items_with_invalid_mappings.append({"rel_path": rel_path, "bad_tags": bad_tags}) + if items_with_invalid_mappings: print("❌ Some items had invalid MITRE mapping formats:") print() @@ -42,16 +40,21 @@ def main(path: Path) -> bool: print("\t" + bad_tag) print() - print(("To ensure that your MITRE mappings are correctly displayed in the Panther " - "console, make sure your MITRE mappings are formatted like 'TA0000:T0000'.")) + print( + ( + "To ensure that your MITRE mappings are correctly displayed in the Panther " + "console, make sure your MITRE mappings are formatted like 'TA0000:T0000'." + ) + ) else: print("✅ No invalid MITRE mappings found! You're in the clear! 👍") - + return bool(items_with_invalid_mappings) + if __name__ == "__main__": - path = Path.cwd() # Default to current directory + path = Path.cwd() # Default to current directory if len(sys.argv) > 1: path = Path(sys.argv[1]) if main(path): - exit(1) # Exit with error if issues were found \ No newline at end of file + exit(1) # Exit with error if issues were found diff --git a/global_helpers/gcp_base_helpers.py b/global_helpers/gcp_base_helpers.py index d085fc458..82246a21f 100644 --- a/global_helpers/gcp_base_helpers.py +++ b/global_helpers/gcp_base_helpers.py @@ -1,6 +1,3 @@ -from panther_base_helpers import deep_get - - def get_info(event): fields = { "principal": "protoPayload.authenticationInfo.principalEmail", @@ -9,7 +6,7 @@ def get_info(event): "user_agent": "protoPayload.requestMetadata.callerSuppliedUserAgent", "method_name": "protoPayload.methodName", } - return {name: deep_get(event, *(path.split("."))) for name, path in fields.items()} + return {name: event.deep_get(*(path.split("."))) for name, path in fields.items()} def get_k8s_info(event): @@ -17,7 +14,7 @@ def get_k8s_info(event): Get GCP K8s info such as pod, authorized user etc. return a tuple of strings """ - pod_slug = deep_get(event, "protoPayload", "resourceName") + pod_slug = event.deep_get("protoPayload", "resourceName") # core/v1/namespaces//pods// _, _, _, namespace, _, pod, _ = pod_slug.split("/") return get_info(event) | {"namespace": namespace, "pod": pod} @@ -33,17 +30,17 @@ def get_flow_log_info(event): "bytes_sent": "jsonPayload.bytes_sent", "reporter": "jsonPayload.reporter", } - return {name: deep_get(event, *(path.split("."))) for name, path in fields.items()} + return {name: event.deep_get(*(path.split("."))) for name, path in fields.items()} def gcp_alert_context(event): return { - "project": deep_get(event, "resource", "labels", "project_id", default=""), - "principal": deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + "project": event.deep_get("resource", "labels", "project_id", default=""), + "principal": event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ), - "caller_ip": deep_get(event, "protoPayload", "requestMetadata", "callerIP", default=""), - "methodName": deep_get(event, "protoPayload", "methodName", default=""), - "resourceName": deep_get(event, "protoPayload", "resourceName", default=""), - "serviceName": deep_get(event, "protoPayload", "serviceName", default=""), + "caller_ip": event.deep_get("protoPayload", "requestMetadata", "callerIP", default=""), + "methodName": event.deep_get("protoPayload", "methodName", default=""), + "resourceName": event.deep_get("protoPayload", "resourceName", default=""), + "serviceName": event.deep_get("protoPayload", "serviceName", default=""), } diff --git a/global_helpers/global_filter_auth0.py b/global_helpers/global_filter_auth0.py index 1ebcffd20..dc240a1bb 100644 --- a/global_helpers/global_filter_auth0.py +++ b/global_helpers/global_filter_auth0.py @@ -1,6 +1,3 @@ -from panther_base_helpers import deep_get # pylint: disable=unused-import - - def filter_include_event(event) -> bool: # pylint: disable=unused-argument """ filter_include_event provides a global include filter for all Auth0 detections @@ -20,7 +17,7 @@ def filter_include_event(event) -> bool: # pylint: disable=unused-argument # # not all Auth0 enterprise events have org # # example: request domain # # if we don't know the request_domain, we want default behavior to be to alert on this event. - # request_domain = deep_get(event, "data", "details", "request", "channel", default="") + # request_domain = event.deep_get("data", "details", "request", "channel", default="") # return request_domain in ["https://manage.auth0.com/", "https://mycompany.auth0.com", ""] # return True diff --git a/global_helpers/global_filter_azuresignin.py b/global_helpers/global_filter_azuresignin.py index a98ad8da8..644bb018d 100644 --- a/global_helpers/global_filter_azuresignin.py +++ b/global_helpers/global_filter_azuresignin.py @@ -1,6 +1,3 @@ -from panther_base_helpers import deep_get # pylint: disable=unused-import - - def filter_include_event(event) -> bool: # pylint: disable=unused-argument """ filter_include_event provides a global include filter for all AzureSignIn detections @@ -17,7 +14,7 @@ def filter_include_event(event) -> bool: # pylint: disable=unused-argument # # # example: event['tenantId'] # # if tenantId were missing, we want default behavior to be to alert on this event. - # tenant_id = deep_get(event, "tenantId", default="") + # tenant_id = event.get("tenantId", "") # return event_origin in ["333333eb-a222-33cc-9baf-4a1111111111", ""] # return True diff --git a/global_helpers/global_filter_cloudflare.py b/global_helpers/global_filter_cloudflare.py index 1480a605c..aecbdb606 100644 --- a/global_helpers/global_filter_cloudflare.py +++ b/global_helpers/global_filter_cloudflare.py @@ -1,6 +1,3 @@ -from panther_base_helpers import deep_get # pylint: disable=unused-import - - def filter_include_event(event) -> bool: # pylint: disable=unused-argument """ filter_include_event provides a global include filter for all cloudflare detections diff --git a/global_helpers/global_filter_github.py b/global_helpers/global_filter_github.py index f871c3c3a..df5a6d7ff 100644 --- a/global_helpers/global_filter_github.py +++ b/global_helpers/global_filter_github.py @@ -1,6 +1,3 @@ -from panther_base_helpers import deep_get # pylint: disable=unused-import - - def filter_include_event(event) -> bool: # pylint: disable=unused-argument """ filter_include_event provides a global include filter for all github detections @@ -19,7 +16,7 @@ def filter_include_event(event) -> bool: # pylint: disable=unused-argument # # # not all github enterprise events have org # # example: enterprise.self_hosted_runner_online - # org = deep_get(event, "org", default="") + # org = event.get("org", "") # return org in ["my-prod-org", ""] # return True diff --git a/global_helpers/global_filter_notion.py b/global_helpers/global_filter_notion.py index 79936f6f0..1985ae648 100644 --- a/global_helpers/global_filter_notion.py +++ b/global_helpers/global_filter_notion.py @@ -1,6 +1,3 @@ -from panther_base_helpers import deep_get # pylint: disable=unused-import - - def filter_include_event(event) -> bool: # pylint: disable=unused-argument """ filter_include_event provides a global include filter for all Notion detections @@ -17,7 +14,7 @@ def filter_include_event(event) -> bool: # pylint: disable=unused-argument # # # example: workspace_id # # if we don't know the workspace_id, we want default behavior to be to alert on this event. - # workspace_id = deep_get(event, "workspace_id", default="") + # workspace_id = event.get("workspace_id", "") # return workspace_id in ["ea65b016-6abc-4dcf-808b-e000099999999", ""] # return True diff --git a/global_helpers/global_filter_snyk.py b/global_helpers/global_filter_snyk.py index 25f08ef99..7192f313a 100644 --- a/global_helpers/global_filter_snyk.py +++ b/global_helpers/global_filter_snyk.py @@ -1,6 +1,3 @@ -from panther_base_helpers import deep_get # pylint: disable=unused-import - - def filter_include_event(event) -> bool: # pylint: disable=unused-argument """ filter_include_event provides a global include filter for all snyk detections @@ -16,7 +13,7 @@ def filter_include_event(event) -> bool: # pylint: disable=unused-argument # # # not all snyk audit events have orgId & projectId # # example: group.user.add, sometimes api.access - # org = deep_get(event, "orgId", default="") + # org = event.get("orgId", "") # return org in ["21111111-a222-4eee-8ddd-a99999999999", ""] # return True diff --git a/global_helpers/global_filter_tailscale.py b/global_helpers/global_filter_tailscale.py index 4ab47fe63..20be9fe9d 100644 --- a/global_helpers/global_filter_tailscale.py +++ b/global_helpers/global_filter_tailscale.py @@ -1,6 +1,3 @@ -from panther_base_helpers import deep_get # pylint: disable=unused-import - - def filter_include_event(event) -> bool: # pylint: disable=unused-argument """ filter_include_event provides a global include filter for all Tailscale detections @@ -17,7 +14,7 @@ def filter_include_event(event) -> bool: # pylint: disable=unused-argument # # # example: event.origin # # if we don't know the event_origin, we want default behavior to be to alert on this event. - # event_origin = deep_get(event, "event", "origin", default="") + # event_origin = event.deep_get("event", "origin", default="") # return event_origin in ["ADMIN_CONSOLE", ""] # return True diff --git a/global_helpers/global_filter_tines.py b/global_helpers/global_filter_tines.py index cfeca257d..c2b411239 100644 --- a/global_helpers/global_filter_tines.py +++ b/global_helpers/global_filter_tines.py @@ -1,6 +1,3 @@ -from panther_base_helpers import deep_get # pylint: disable=unused-import - - def filter_include_event(event) -> bool: # pylint: disable=unused-argument """ filter_include_event provides a global include filter for all Tines detections @@ -14,7 +11,7 @@ def filter_include_event(event) -> bool: # pylint: disable=unused-argument # 1. the specific tenant_id mentioned. # 2. events where tenant_id is undefined # - # tenant_id = deep_get(event, "tenant_id", default="") + # tenant_id = event.get("tenant_id", "") # return tenant_id in ["1234", ""] # return True diff --git a/global_helpers/global_helpers_test.py b/global_helpers/global_helpers_test.py index dba629cb5..f26321396 100755 --- a/global_helpers/global_helpers_test.py +++ b/global_helpers/global_helpers_test.py @@ -10,6 +10,7 @@ import sys import unittest +from panther_core.enriched_event import PantherEvent from panther_core.immutable import ImmutableCaseInsensitiveDict, ImmutableList # pipenv run does the right thing, but IDE based debuggers may fail to import @@ -38,7 +39,7 @@ class TestEksPantherObjRef(unittest.TestCase): def setUp(self): # pylint: disable=C0301 - self.event = ImmutableCaseInsensitiveDict( + self.event = PantherEvent( { "annotations": { "authorization.k8s.io/decision": "allow", @@ -110,7 +111,7 @@ def test_all_missing_event(self): del temp_event["sourceIPs"] del temp_event["verb"] del temp_event["p_source_label"] - temp_event = ImmutableCaseInsensitiveDict(temp_event) + temp_event = PantherEvent(temp_event) response = p_b_h.eks_panther_obj_ref(temp_event) self.assertEqual(response.get("actor", ""), "") self.assertEqual(response.get("object", ""), "") @@ -124,7 +125,7 @@ def test_all_missing_event(self): def test_missing_subresource_event(self): temp_event = self.event.to_dict() del temp_event["objectRef"]["subresource"] - temp_event = ImmutableCaseInsensitiveDict(temp_event) + temp_event = PantherEvent(temp_event) response = p_b_h.eks_panther_obj_ref(temp_event) self.assertEqual(response.get("resource", ""), "pods") @@ -203,12 +204,12 @@ def test_additional_details_str_list_json(self): class TestTorExitNodes(unittest.TestCase): def setUp(self): - self.event = ImmutableCaseInsensitiveDict( + self.event = PantherEvent( {"p_enrichment": {"tor_exit_nodes": {"foo": {"ip": "1.2.3.4"}, "p_match": "1.2.3.4"}}} ) # match against array field - self.event_list = ImmutableCaseInsensitiveDict( + self.event_list = PantherEvent( { "p_enrichment": { "tor_exit_nodes": { @@ -223,7 +224,7 @@ def setUp(self): def test_ip_address_not_found(self): """Should not find anything""" - tor_exit_nodes = p_tor_h.TorExitNodes({}) + tor_exit_nodes = p_tor_h.TorExitNodes(PantherEvent({})) ip_address = tor_exit_nodes.ip_address("foo") self.assertEqual(ip_address, None) self.assertEqual(tor_exit_nodes.has_exit_nodes(), False) @@ -284,7 +285,7 @@ def test_context(self): class TestGreyNoiseBasic(unittest.TestCase): def setUp(self): - self.event = ImmutableCaseInsensitiveDict( + self.event = PantherEvent( { "p_enrichment": { "greynoise_noise_basic": { @@ -306,7 +307,7 @@ def test_greynoise_object(self): # Ensure that noise.lut_matches is None if there is no enrichment # some users want to test if any greynoise enrichment exists self.assertIsNotNone(noise.lut_matches) - noise_none = p_greynoise_h.GetGreyNoiseObject({}) + noise_none = p_greynoise_h.GetGreyNoiseObject(PantherEvent({})) self.assertIsNone(noise_none.lut_matches) def test_greynoise_severity(self): @@ -316,12 +317,12 @@ def test_greynoise_severity(self): def test_subscription_level(self): """Should be basic""" - noise = p_greynoise_h.GreyNoiseBasic({}) + noise = p_greynoise_h.GreyNoiseBasic(PantherEvent({})) self.assertEqual(noise.subscription_level(), "basic") def test_ip_address_not_found(self): """Should not find anything""" - noise = p_greynoise_h.GreyNoiseBasic({}) + noise = p_greynoise_h.GreyNoiseBasic(PantherEvent({})) ip_address = noise.ip_address("foo") self.assertEqual(ip_address, None) @@ -367,7 +368,7 @@ def test_context(self): # pylint: disable=too-many-public-methods class TestGreyNoiseAdvanced(unittest.TestCase): def setUp(self): - self.event = ImmutableCaseInsensitiveDict( + self.event = PantherEvent( { "p_enrichment": { "greynoise_noise_advanced": { @@ -409,7 +410,7 @@ def setUp(self): } ) - self.event_list = ImmutableCaseInsensitiveDict( + self.event_list = PantherEvent( { "p_enrichment": { "greynoise_noise_advanced": { @@ -495,12 +496,12 @@ def test_greynoise_severity_list(self): def test_subscription_level(self): """Should be advanced""" - noise = p_greynoise_h.GreyNoiseAdvanced({}) + noise = p_greynoise_h.GreyNoiseAdvanced(PantherEvent({})) self.assertEqual(noise.subscription_level(), "advanced") def test_ip_address_not_found(self): """Should not find anything""" - noise = p_greynoise_h.GreyNoiseAdvanced({}) + noise = p_greynoise_h.GreyNoiseAdvanced(PantherEvent({})) ip_address = noise.ip_address("foo") self.assertEqual(ip_address, None) @@ -712,7 +713,7 @@ def test_context(self): class TestRIOTBasic(unittest.TestCase): def setUp(self): - self.event = ImmutableCaseInsensitiveDict( + self.event = PantherEvent( { "p_enrichment": { "greynoise_riot_basic": { @@ -734,7 +735,7 @@ def test_greynoise_object(self): def test_subscription_level(self): """Should be basic""" - riot = p_greynoise_h.GreyNoiseRIOTBasic({}) + riot = p_greynoise_h.GreyNoiseRIOTBasic(PantherEvent({})) self.assertEqual(riot.subscription_level(), "basic") def test_greynoise_severity(self): @@ -789,7 +790,7 @@ def test_context(self): class TestRIOTAdvanced(unittest.TestCase): def setUp(self): - self.event = ImmutableCaseInsensitiveDict( + self.event = PantherEvent( { "p_enrichment": { "greynoise_riot_advanced": { @@ -812,7 +813,7 @@ def setUp(self): ) # for testing array matches - self.event_list = ImmutableCaseInsensitiveDict( + self.event_list = PantherEvent( { "p_enrichment": { "greynoise_riot_advanced": { @@ -856,7 +857,7 @@ def test_greynoise_object(self): def test_subscription_level(self): """Should be advanced""" - riot = p_greynoise_h.GreyNoiseRIOTAdvanced({}) + riot = p_greynoise_h.GreyNoiseRIOTAdvanced(PantherEvent({})) self.assertEqual(riot.subscription_level(), "advanced") def test_greynoise_severity(self): @@ -962,7 +963,7 @@ def test_context(self): class TestIpInfoHelpersLocation(unittest.TestCase): def setUp(self): self.match_field = "clientIp" - self.event = ImmutableCaseInsensitiveDict( + self.event = PantherEvent( { "p_enrichment": { p_i_h.IPINFO_LOCATION_LUT_NAME: { @@ -1035,7 +1036,7 @@ def test_context(self): class TestIpInfoHelpersASN(unittest.TestCase): def setUp(self): self.match_field = "clientIp" - self.event = ImmutableCaseInsensitiveDict( + self.event = PantherEvent( { "p_enrichment": { p_i_h.IPINFO_ASN_LUT_NAME: { @@ -1120,7 +1121,7 @@ def test_is_entirely_different_type(self): class TestGetCrowdstrikeField(unittest.TestCase): def setUp(self): - self.input = ImmutableCaseInsensitiveDict( + self.input = PantherEvent( { "cid": "something", "aid": "else", @@ -1152,7 +1153,7 @@ def test_input_key_can_be_found_in_unknown(self): def test_precedence(self): temp_event = self.input.to_dict() temp_event["event"]["field"] = "found" - temp_event = ImmutableCaseInsensitiveDict(temp_event) + temp_event = PantherEvent(temp_event) response = p_b_h.get_crowdstrike_field(temp_event, "field") self.assertEqual(response, "found") @@ -1160,7 +1161,7 @@ def test_precedence(self): class TestIpInfoHelpersPrivacy(unittest.TestCase): def setUp(self): self.match_field = "clientIp" - self.event = ImmutableCaseInsensitiveDict( + self.event = PantherEvent( { "p_enrichment": { p_i_h.IPINFO_PRIVACY_LUT_NAME: { @@ -1221,7 +1222,7 @@ def test_context(self): class TestGeoInfoFromIP(unittest.TestCase): def setUp(self): self.match_field = "clientIp" - self.event = ImmutableCaseInsensitiveDict( + self.event = PantherEvent( { "p_enrichment": { p_i_h.IPINFO_ASN_LUT_NAME: { @@ -1267,7 +1268,7 @@ def test_geoinfo(self): self.assertEqual(expected, geoinfo) def test_ipinfo_not_enabled_exception(self): - event = ImmutableCaseInsensitiveDict({"p_enrichment": {}}) + event = PantherEvent({"p_enrichment": {}}) with self.assertRaises(p_i_h.PantherIPInfoException) as exc: p_i_h.geoinfo_from_ip(event, "fake_field") @@ -1523,7 +1524,7 @@ def test_deep_walk_manual(self): class TestCloudflareHelpers(unittest.TestCase): def setUp(self): - self.event = ImmutableCaseInsensitiveDict( + self.event = PantherEvent( { "Source": "firewallrules", "ClientIP": "12.12.12.12", @@ -1635,7 +1636,7 @@ def test_http_context_helper(self): class TestAsanaHelpers(unittest.TestCase): def setUp(self): - self.event = ImmutableCaseInsensitiveDict( + self.event = PantherEvent( { "actor": { "actor_type": "user", @@ -1670,13 +1671,13 @@ def test_alert_context(self): # Remove the user's email attribute tmp_event = self.event.to_dict() tmp_event["actor"].pop("email") - tmp_event = ImmutableCaseInsensitiveDict(tmp_event) + tmp_event = PantherEvent(tmp_event) returns = p_a_h.asana_alert_context(tmp_event) self.assertEqual(returns.get("actor", ""), "") self.assertEqual(returns.get("resource_type", ""), "task") tmp_event = tmp_event.to_dict() tmp_event["resource"] = {"resource_type": "story", "resource_subtype": "added_to_project"} - tmp_event = ImmutableCaseInsensitiveDict(tmp_event) + tmp_event = PantherEvent(tmp_event) returns = p_a_h.asana_alert_context(tmp_event) self.assertEqual(returns.get("resource_type", ""), "story__added_to_project") # resource with no resource subtype @@ -1687,14 +1688,14 @@ def test_alert_context(self): "name": "Users Name", "resource_type": "user", } - tmp_event = ImmutableCaseInsensitiveDict(tmp_event) + tmp_event = PantherEvent(tmp_event) returns = p_a_h.asana_alert_context(tmp_event) self.assertEqual(returns.get("resource_type", ""), "user") self.assertEqual(returns.get("resource_name", ""), "Users Name") self.assertEqual(returns.get("resource_gid", ""), "1111111111111111") def test_safe_ac_missing_entries(self): - returns = p_a_h.asana_alert_context(ImmutableCaseInsensitiveDict({})) + returns = p_a_h.asana_alert_context(PantherEvent({})) self.assertEqual(returns.get("actor"), "") self.assertEqual(returns.get("event_type"), "") self.assertEqual(returns.get("resource_type"), "") @@ -1702,12 +1703,12 @@ def test_safe_ac_missing_entries(self): self.assertEqual(returns.get("resource_gid"), "") tmp_event = self.event.to_dict() tmp_event["resource"]["resource_type"] = None - tmp_event = ImmutableCaseInsensitiveDict(tmp_event) + tmp_event = PantherEvent(tmp_event) returns = p_a_h.asana_alert_context(tmp_event) self.assertEqual(returns.get("resource_type"), "") def test_external_admin(self): - event = ImmutableCaseInsensitiveDict( + event = PantherEvent( { "actor": {"actor_type": "external_administrator"}, "context": {"context_type": "api"}, @@ -1731,7 +1732,7 @@ def test_external_admin(self): class TestSnykHelpers(unittest.TestCase): def setUp(self): - self.event = ImmutableCaseInsensitiveDict( + self.event = PantherEvent( { "content": {"url": "/api/v1/user/me"}, "created": "2022-12-27 16:50:46.959", @@ -1769,7 +1770,7 @@ def test_alert_context(self): class TestTinesHelpers(unittest.TestCase): def setUp(self): - self.event = ImmutableCaseInsensitiveDict( + self.event = PantherEvent( { "created_at": "2023-05-01 01:02:03", "id": 7206820, @@ -1814,7 +1815,7 @@ def test_alert_context(self): class TestAuth0Helpers(unittest.TestCase): def setUp(self): - self.event = ImmutableCaseInsensitiveDict( + self.event = PantherEvent( { "data": { "client_id": "1HXWWGKk1Zj3JF8GvMrnCSirccDs4qvr", @@ -1870,8 +1871,8 @@ def test_alert_context(self): ) self.assertEqual(returns.get("action", ""), "Create a role") self.assertEqual(auth0_config_event, True) - returns = p_auth0_h.auth0_alert_context(ImmutableCaseInsensitiveDict({})) - auth0_config_event = p_auth0_h.is_auth0_config_event({}) + returns = p_auth0_h.auth0_alert_context(PantherEvent({})) + auth0_config_event = p_auth0_h.is_auth0_config_event(PantherEvent({})) self.assertEqual(returns.get("actor", ""), "") self.assertEqual(returns.get("action", ""), "") self.assertEqual(auth0_config_event, False) @@ -1879,7 +1880,7 @@ def test_alert_context(self): class TestTailscaleHelpers(unittest.TestCase): def setUp(self): - self.event = ImmutableCaseInsensitiveDict( + self.event = PantherEvent( { "event": { "action": "CREATE", @@ -1912,8 +1913,10 @@ def test_alert_context(self): ) self.assertEqual(returns.get("action", ""), "CREATE") self.assertEqual(tailscale_admin_console_event, True) - returns = p_tscale_h.tailscale_alert_context(ImmutableCaseInsensitiveDict({})) - tailscale_admin_console_event = p_tscale_h.is_tailscale_admin_console_event({}) + returns = p_tscale_h.tailscale_alert_context(PantherEvent({})) + tailscale_admin_console_event = p_tscale_h.is_tailscale_admin_console_event( + PantherEvent({}) + ) self.assertEqual(returns.get("actor", ""), "") self.assertEqual(returns.get("action", ""), "") self.assertEqual(tailscale_admin_console_event, False) @@ -1987,7 +1990,7 @@ def test_distances(self): class TestNotionHelpers(unittest.TestCase): def setUp(self): - self.event = ImmutableCaseInsensitiveDict( + self.event = PantherEvent( { "event": { "id": "...", @@ -2019,7 +2022,7 @@ def test_alert_context(self): }, ) self.assertEqual(returns.get("action", ""), "workspace.content_exported") - returns = p_notion_h.notion_alert_context(ImmutableCaseInsensitiveDict({})) + returns = p_notion_h.notion_alert_context(PantherEvent({})) self.assertEqual(returns.get("actor", ""), "") self.assertEqual(returns.get("action", ""), "") @@ -2027,10 +2030,10 @@ def test_alert_context(self): class TestLookupTableHelpers(unittest.TestCase): # pylint: disable=protected-access def setUp(self): - self.simple_event_no_pmatch = ImmutableCaseInsensitiveDict( + self.simple_event_no_pmatch = PantherEvent( {"p_enrichment": {"tor_exit_nodes": {"foo": {"ip": "1.2.3.4"}}}} ) - self.simple_event = ImmutableCaseInsensitiveDict( + self.simple_event = PantherEvent( { "p_enrichment": { "tor_exit_nodes": { @@ -2051,7 +2054,7 @@ def setUp(self): } ) # match against array field - self.list_event = ImmutableCaseInsensitiveDict( + self.list_event = PantherEvent( { "p_enrichment": { "tor_exit_nodes": { @@ -2109,7 +2112,7 @@ def test_enrichments_by_pmatch(self): class TestAzureSigninHelpers(unittest.TestCase): def setUp(self): # pylint: disable=line-too-long - self.event_noninteractive = ImmutableCaseInsensitiveDict( + self.event_noninteractive = PantherEvent( { "Level": 4, "callerIpAddress": "12.12.12.12", @@ -2213,7 +2216,7 @@ def setUp(self): "time": "2023-07-24 07:13:50.894", } ) - self.event_signin = ImmutableCaseInsensitiveDict( + self.event_signin = PantherEvent( { "Level": 4, "callerIpAddress": "12.12.12.12", @@ -2353,7 +2356,7 @@ def test_alert_context(self): "resourceId": "b0d1813f-efb7-4d74-b573-50f2931a6837", }, ) - returns = p_asi_h.azure_signin_alert_context({}) + returns = p_asi_h.azure_signin_alert_context(PantherEvent({})) self.assertEqual( returns, { diff --git a/global_helpers/panther_asana_helpers.py b/global_helpers/panther_asana_helpers.py index ae82e3790..72dfef37b 100644 --- a/global_helpers/panther_asana_helpers.py +++ b/global_helpers/panther_asana_helpers.py @@ -1,7 +1,4 @@ -from panther_base_helpers import deep_get - - -def asana_alert_context(event: dict) -> dict: +def asana_alert_context(event) -> dict: a_c = { "actor": "", "context": "", @@ -10,23 +7,23 @@ def asana_alert_context(event: dict) -> dict: "resource_name": "", "resource_gid": "", } - if deep_get(event, "actor", "actor_type", default="") == "user": - a_c["actor"] = deep_get(event, "actor", "email", default="") + if event.deep_get("actor", "actor_type", default="") == "user": + a_c["actor"] = event.deep_get("actor", "email", default="") else: - a_c["actor"] = deep_get(event, "actor", "actor_type", default="") + a_c["actor"] = event.deep_get("actor", "actor_type", default="") if "event_type" in event: # Events have categories and event_type # We have not seen category overlap -> only including event_type a_c["event_type"] = event.get("event_type") - a_c["resource_name"] = deep_get(event, "resource", "name", default="") - a_c["resource_gid"] = deep_get(event, "resource", "gid", default="") - r_t = deep_get(event, "resource", "resource_type") + a_c["resource_name"] = event.deep_get("resource", "name", default="") + a_c["resource_gid"] = event.deep_get("resource", "gid", default="") + r_t = event.deep_get("resource", "resource_type") if r_t: a_c["resource_type"] = r_t - r_s_t = deep_get(event, "resource", "resource_subtype") + r_s_t = event.deep_get("resource", "resource_subtype") if r_t and r_s_t and r_s_t != r_t: a_c["resource_type"] += "__" + r_s_t - ctx = deep_get(event, "context", "context_type") + ctx = event.deep_get("context", "context_type") if ctx: a_c["context"] = ctx return a_c diff --git a/global_helpers/panther_auth0_helpers.py b/global_helpers/panther_auth0_helpers.py index d5455e24b..b47f8cbc7 100644 --- a/global_helpers/panther_auth0_helpers.py +++ b/global_helpers/panther_auth0_helpers.py @@ -1,15 +1,12 @@ -from panther_base_helpers import deep_get - - def auth0_alert_context(event) -> dict: a_c = {} - a_c["actor"] = deep_get( - event, "data", "details", "request", "auth", "user", default="" + a_c["actor"] = event.deep_get( + "data", "details", "request", "auth", "user", default="" ) - a_c["action"] = deep_get(event, "data", "description", default="") + a_c["action"] = event.deep_get("data", "description", default="") return a_c def is_auth0_config_event(event): - channel = deep_get(event, "data", "details", "request", "channel", default="") + channel = event.deep_get("data", "details", "request", "channel", default="") return channel == "https://manage.auth0.com/" diff --git a/global_helpers/panther_azuresignin_helpers.py b/global_helpers/panther_azuresignin_helpers.py index 4ce265f42..3058f63fc 100644 --- a/global_helpers/panther_azuresignin_helpers.py +++ b/global_helpers/panther_azuresignin_helpers.py @@ -1,17 +1,14 @@ -from panther_base_helpers import deep_get - - def actor_user(event): - category = deep_get(event, "category", default="") + category = event.get("category", "") if category in {"ServicePrincipalSignInLogs"}: - return deep_get(event, "properties", "servicePrincipalName") + return event.deep_get("properties", "servicePrincipalName") if category in {"SignInLogs", "NonInteractiveUserSignInLogs"}: - return deep_get(event, "properties", "userPrincipalName") + return event.deep_get("properties", "userPrincipalName") return None def is_sign_in_event(event): - return deep_get(event, "operationName", default="") == "Sign-in activity" + return event.get("operationName", "") == "Sign-in activity" def azure_signin_alert_context(event) -> dict: @@ -19,11 +16,11 @@ def azure_signin_alert_context(event) -> dict: if ac_actor_user is None: ac_actor_user = "" a_c = {} - a_c["tenantId"] = deep_get(event, "tenantId", default="") - a_c["source_ip"] = deep_get(event, "properties", "ipAddress", default="") + a_c["tenantId"] = event.get("tenantId", "") + a_c["source_ip"] = event.deep_get("properties", "ipAddress", default="") a_c["actor_user"] = ac_actor_user - a_c["resourceDisplayName"] = deep_get( - event, "properties", "resourceDisplayName", default="" + a_c["resourceDisplayName"] = event.deep_get( + "properties", "resourceDisplayName", default="" ) - a_c["resourceId"] = deep_get(event, "properties", "resourceId", default="") + a_c["resourceId"] = event.deep_get("properties", "resourceId", default="") return a_c diff --git a/global_helpers/panther_base_helpers.py b/global_helpers/panther_base_helpers.py index ea7cbbfb8..f17549a6a 100644 --- a/global_helpers/panther_base_helpers.py +++ b/global_helpers/panther_base_helpers.py @@ -269,27 +269,27 @@ def filter_crowdstrike_fdr_event_type(event, name: str) -> bool: def get_crowdstrike_field(event, field_name, default=None): return ( - deep_get(event, field_name) - or deep_get(event, "event", field_name) - or deep_get(event, "unknown_payload", field_name) + event.deep_get(field_name) + or event.deep_get("event", field_name) + or event.deep_get("unknown_payload", field_name) or default ) -def slack_alert_context(event: dict): +def slack_alert_context(event): return { - "actor-name": deep_get(event, "actor", "user", "name", default=""), - "actor-email": deep_get(event, "actor", "user", "email", default=""), - "actor-ip": deep_get(event, "context", "ip_address", default=""), - "user-agent": deep_get(event, "context", "ua", default=""), + "actor-name": event.deep_get("actor", "user", "name", default=""), + "actor-email": event.deep_get("actor", "user", "email", default=""), + "actor-ip": event.deep_get("context", "ip_address", default=""), + "user-agent": event.deep_get("context", "ua", default=""), } -def github_alert_context(event: dict): +def github_alert_context(event): return { "action": event.get("action", ""), "actor": event.get("actor", ""), - "actor_location": deep_get(event, "actor_location", "country_code"), + "actor_location": event.deep_get("actor_location", "country_code"), "org": event.get("org", ""), "repo": event.get("repo", ""), "user": event.get("user", ""), @@ -412,14 +412,14 @@ def aws_guardduty_context(event: dict): } -def eks_panther_obj_ref(event: dict): - user = deep_get(event, "user", "username", default="") +def eks_panther_obj_ref(event): + user = event.deep_get("user", "username", default="") source_ips = event.get("sourceIPs", ["0.0.0.0"]) # nosec verb = event.get("verb", "") - obj_name = deep_get(event, "objectRef", "name", default="") - obj_ns = deep_get(event, "objectRef", "namespace", default="") - obj_res = deep_get(event, "objectRef", "resource", default="") - obj_subres = deep_get(event, "objectRef", "subresource", default="") + obj_name = event.deep_get("objectRef", "name", default="") + obj_ns = event.deep_get("objectRef", "namespace", default="") + obj_res = event.deep_get("objectRef", "resource", default="") + obj_subres = event.deep_get("objectRef", "subresource", default="") p_source_label = event.get("p_source_label", "") if obj_subres: obj_res = "/".join([obj_res, obj_subres]) diff --git a/global_helpers/panther_greynoise_helpers.py b/global_helpers/panther_greynoise_helpers.py index c73b77a68..6d7a09c2d 100644 --- a/global_helpers/panther_greynoise_helpers.py +++ b/global_helpers/panther_greynoise_helpers.py @@ -4,7 +4,6 @@ from typing import Union from dateutil import parser -from panther_base_helpers import deep_get from panther_lookuptable_helpers import LookupTableMatches @@ -285,13 +284,13 @@ def context(self, match_field: str) -> dict: # pylint: disable=invalid-name def GetGreyNoiseObject(event): - if deep_get(event, "p_enrichment", "greynoise_noise_advanced"): + if event.deep_get("p_enrichment", "greynoise_noise_advanced"): return GreyNoiseAdvanced(event) return GreyNoiseBasic(event) def GetGreyNoiseRiotObject(event): - if deep_get(event, "p_enrichment", "greynoise_riot_advanced"): + if event.deep_get("p_enrichment", "greynoise_riot_advanced"): return GreyNoiseRIOTAdvanced(event) return GreyNoiseRIOTBasic(event) diff --git a/global_helpers/panther_ipinfo_helpers.py b/global_helpers/panther_ipinfo_helpers.py index 55cdb4115..9aa7d01f4 100644 --- a/global_helpers/panther_ipinfo_helpers.py +++ b/global_helpers/panther_ipinfo_helpers.py @@ -126,21 +126,21 @@ def context(self, match_field: str) -> object: def get_ipinfo_location(event): """Returns an IPInfoLocation object for the event or None if it is not available""" - if deep_get(event, "p_enrichment", IPINFO_LOCATION_LUT_NAME): + if event.deep_get("p_enrichment", IPINFO_LOCATION_LUT_NAME): return IPInfoLocation(event) return None def get_ipinfo_asn(event): """Returns an IPInfoASN object for the event or None if it is not available""" - if deep_get(event, "p_enrichment", IPINFO_ASN_LUT_NAME): + if event.deep_get("p_enrichment", IPINFO_ASN_LUT_NAME): return IPInfoASN(event) return None def get_ipinfo_privacy(event): """Returns an IPInfoPrivacy object for the event or None if it is not available""" - if deep_get(event, "p_enrichment", IPINFO_PRIVACY_LUT_NAME): + if event.deep_get("p_enrichment", IPINFO_PRIVACY_LUT_NAME): return IPInfoPrivacy(event) return None diff --git a/global_helpers/panther_lookuptable_helpers.py b/global_helpers/panther_lookuptable_helpers.py index db7a87324..295db4a2a 100644 --- a/global_helpers/panther_lookuptable_helpers.py +++ b/global_helpers/panther_lookuptable_helpers.py @@ -13,7 +13,7 @@ def __init__(self): self._p_matched = {} def _register(self, event, lookuptable_name: str): - self.lut_matches = deep_get(event, ENRICHMENT_KEY, lookuptable_name) + self.lut_matches = event.deep_get(ENRICHMENT_KEY, lookuptable_name) def _lookup(self, match_field: str, *keys) -> list or str: match = deep_get(self.lut_matches, match_field) diff --git a/global_helpers/panther_notion_helpers.py b/global_helpers/panther_notion_helpers.py index 3329e9114..51b7c79b8 100644 --- a/global_helpers/panther_notion_helpers.py +++ b/global_helpers/panther_notion_helpers.py @@ -1,8 +1,5 @@ -from panther_base_helpers import deep_get - - def notion_alert_context(event) -> dict: a_c = {} - a_c["actor"] = deep_get(event, "event", "actor", default="") - a_c["action"] = deep_get(event, "event", "type", default="") + a_c["actor"] = event.deep_get("event", "actor", default="") + a_c["action"] = event.deep_get("event", "type", default="") return a_c diff --git a/global_helpers/panther_sublime_helpers.py b/global_helpers/panther_sublime_helpers.py index 677b650f2..fbc271ca0 100644 --- a/global_helpers/panther_sublime_helpers.py +++ b/global_helpers/panther_sublime_helpers.py @@ -1,6 +1,6 @@ def sublime_alert_context(event) -> dict: context = {} - context["events_type"] = event.get("type", default="") + context["events_type"] = event.get("type", "") context["users_emails"] = event.deep_get( "created_by", "email_address", default="" ) diff --git a/global_helpers/panther_tailscale_helpers.py b/global_helpers/panther_tailscale_helpers.py index 2f4bdbada..45daaab22 100644 --- a/global_helpers/panther_tailscale_helpers.py +++ b/global_helpers/panther_tailscale_helpers.py @@ -1,13 +1,10 @@ -from panther_base_helpers import deep_get - - def tailscale_alert_context(event) -> dict: a_c = {} - a_c["actor"] = deep_get(event, "event", "actor", default="") - a_c["action"] = deep_get(event, "event", "action", default="") + a_c["actor"] = event.deep_get("event", "actor", default="") + a_c["action"] = event.deep_get("event", "action", default="") return a_c def is_tailscale_admin_console_event(event): - origin = deep_get(event, "event", "origin", default="") + origin = event.deep_get("event", "origin", default="") return origin == "ADMIN_CONSOLE" diff --git a/queries/crowdstrike_queries/aws_authentication_from_crowdstrike_unmanaged_device.py b/queries/crowdstrike_queries/aws_authentication_from_crowdstrike_unmanaged_device.py index aa45f04b4..9095a199e 100644 --- a/queries/crowdstrike_queries/aws_authentication_from_crowdstrike_unmanaged_device.py +++ b/queries/crowdstrike_queries/aws_authentication_from_crowdstrike_unmanaged_device.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context def rule(_): @@ -8,7 +8,7 @@ def rule(_): def title(event): return ( f"AWS [{event.get('eventName')}] for " - f"[{deep_get(event, 'userIdentity', 'arn', default = '')}]" + f"[{event.deep_get('userIdentity', 'arn', default = '')}]" " from unmanaged IP Address." ) diff --git a/queries/crowdstrike_queries/okta_login_from_crowdstrike_unmanaged_device.py b/queries/crowdstrike_queries/okta_login_from_crowdstrike_unmanaged_device.py index 9006ad985..e70f90295 100644 --- a/queries/crowdstrike_queries/okta_login_from_crowdstrike_unmanaged_device.py +++ b/queries/crowdstrike_queries/okta_login_from_crowdstrike_unmanaged_device.py @@ -1,6 +1,3 @@ -from panther_base_helpers import deep_get - - def rule(_): return True @@ -8,6 +5,6 @@ def rule(_): def title(event): return ( "Okta Login for " - f"[{deep_get(event, 'actor', 'alternateId', default = '')}]" + f"[{event.deep_get('actor', 'alternateId', default = '')}]" " from unmanaged IP Address." ) diff --git a/queries/crowdstrike_queries/onepassword_login_from_crowdstrike_unmanaged_device.py b/queries/crowdstrike_queries/onepassword_login_from_crowdstrike_unmanaged_device.py index dec6a5d15..cd4588abb 100644 --- a/queries/crowdstrike_queries/onepassword_login_from_crowdstrike_unmanaged_device.py +++ b/queries/crowdstrike_queries/onepassword_login_from_crowdstrike_unmanaged_device.py @@ -1,6 +1,3 @@ -from panther_base_helpers import deep_get - - def rule(_): return True @@ -8,6 +5,6 @@ def rule(_): def title(event): return ( "1Password Login for " - f"[{deep_get(event, 'target_user', 'email', default = '')}]" + f"[{event.deep_get('target_user', 'email', default = '')}]" " from unmanaged IP Address." ) diff --git a/rules/appomni_rules/appomni_alert_passthrough.py b/rules/appomni_rules/appomni_alert_passthrough.py index 15299ce50..93ecafa2d 100644 --- a/rules/appomni_rules/appomni_alert_passthrough.py +++ b/rules/appomni_rules/appomni_alert_passthrough.py @@ -33,4 +33,4 @@ def dedup(event): def alert_context(event): # 'Threat' and 'related' data to be included in the alert sent to the alert destination - return {"threat": event.deep_get("rule", "threat"), "related": event.deep_get("related")} + return {"threat": event.deep_get("rule", "threat"), "related": event.get("related")} diff --git a/rules/asana_rules/asana_service_account_created.py b/rules/asana_rules/asana_service_account_created.py index 33fb9d9b2..c65ebe6e6 100644 --- a/rules/asana_rules/asana_service_account_created.py +++ b/rules/asana_rules/asana_service_account_created.py @@ -1,11 +1,8 @@ -from panther_base_helpers import deep_get - - def rule(event): return event.get("event_type", "") == "service_account_created" def title(event): - actor_email = deep_get(event, "actor", "email", default="") - svc_acct_name = deep_get(event, "resource", "name", default="") + actor_email = event.deep_get("actor", "email", default="") + svc_acct_name = event.deep_get("resource", "name", default="") return f"Asana user [{actor_email}] created a new service account [{svc_acct_name}]." diff --git a/rules/asana_rules/asana_team_privacy_public.py b/rules/asana_rules/asana_team_privacy_public.py index 459c1e0b5..c423bffc8 100644 --- a/rules/asana_rules/asana_team_privacy_public.py +++ b/rules/asana_rules/asana_team_privacy_public.py @@ -1,14 +1,11 @@ -from panther_base_helpers import deep_get - - def rule(event): return ( event.get("event_type") == "team_privacy_settings_changed" - and deep_get(event, "details", "new_value") == "public" + and event.deep_get("details", "new_value") == "public" ) def title(event): - team = deep_get(event, "resource", "name", default="") - actor = deep_get(event, "actor", "email", default="") + team = event.deep_get("resource", "name", default="") + actor = event.deep_get("actor", "email", default="") return f"Asana team [{team}] has been made public to the org by [{actor}]." diff --git a/rules/asana_rules/asana_workspace_default_session_duration_never.py b/rules/asana_rules/asana_workspace_default_session_duration_never.py index 059d20e68..21d70c59c 100644 --- a/rules/asana_rules/asana_workspace_default_session_duration_never.py +++ b/rules/asana_rules/asana_workspace_default_session_duration_never.py @@ -1,16 +1,13 @@ -from panther_base_helpers import deep_get - - def rule(event): return ( event.get("event_type") == "workspace_default_session_duration_changed" - and deep_get(event, "details", "new_value") == "never" + and event.deep_get("details", "new_value") == "never" ) def title(event): - workspace = deep_get(event, "resource", "name", default="") - actor = deep_get(event, "actor", "email", default="") + workspace = event.deep_get("resource", "name", default="") + actor = event.deep_get("actor", "email", default="") return ( f"Asana workspace [{workspace}]'s default session duration " f"has been set to never expire by [{actor}]." diff --git a/rules/asana_rules/asana_workspace_email_domain_added.py b/rules/asana_rules/asana_workspace_email_domain_added.py index d7924c57b..de629a80d 100644 --- a/rules/asana_rules/asana_workspace_email_domain_added.py +++ b/rules/asana_rules/asana_workspace_email_domain_added.py @@ -1,12 +1,9 @@ -from panther_base_helpers import deep_get - - def rule(event): return event.get("event_type") == "workspace_associated_email_domain_added" def title(event): - workspace = deep_get(event, "resource", "name", default="") - domain = deep_get(event, "details", "new_value", default="") - actor = deep_get(event, "actor", "email", default="") + workspace = event.deep_get("resource", "name", default="") + domain = event.deep_get("details", "new_value", default="") + actor = event.deep_get("actor", "email", default="") return f"Asana new email domain [{domain}] added to Workspace [{workspace}] by [{actor}]." diff --git a/rules/asana_rules/asana_workspace_form_link_auth_requirement_disabled.py b/rules/asana_rules/asana_workspace_form_link_auth_requirement_disabled.py index 5b871171d..325eb7468 100644 --- a/rules/asana_rules/asana_workspace_form_link_auth_requirement_disabled.py +++ b/rules/asana_rules/asana_workspace_form_link_auth_requirement_disabled.py @@ -1,13 +1,10 @@ -from panther_base_helpers import deep_get - - def rule(event): return event.get("event_type") == "workspace_form_link_authentication_required_disabled" def title(event): - workspace = deep_get(event, "resource", "name", default="") - actor = deep_get(event, "actor", "email", default="") + workspace = event.deep_get("resource", "name", default="") + actor = event.deep_get("actor", "email", default="") return ( f"Asana Workspace [{workspace}] Form Link Auth Requirement " f" was disabled by [{actor}]." ) diff --git a/rules/asana_rules/asana_workspace_guest_invite_permissions_anyone.py b/rules/asana_rules/asana_workspace_guest_invite_permissions_anyone.py index cda7098f9..b9f84fcd1 100644 --- a/rules/asana_rules/asana_workspace_guest_invite_permissions_anyone.py +++ b/rules/asana_rules/asana_workspace_guest_invite_permissions_anyone.py @@ -1,16 +1,13 @@ -from panther_base_helpers import deep_get - - def rule(event): return ( event.get("event_type") == "workspace_guest_invite_permissions_changed" - and deep_get(event, "details", "new_value") == "anyone" + and event.deep_get("details", "new_value") == "anyone" ) def title(event): - workspace = deep_get(event, "resource", "name", default="") - actor = deep_get(event, "actor", "email", default="") + workspace = event.deep_get("resource", "name", default="") + actor = event.deep_get("actor", "email", default="") return ( f"Asana Workspace [{workspace}] guest invite permissions " f"changed to anyone by [{actor}]." diff --git a/rules/asana_rules/asana_workspace_new_admin.py b/rules/asana_rules/asana_workspace_new_admin.py index 5b6c01e4b..15c7c369a 100644 --- a/rules/asana_rules/asana_workspace_new_admin.py +++ b/rules/asana_rules/asana_workspace_new_admin.py @@ -1,10 +1,9 @@ from panther_asana_helpers import asana_alert_context -from panther_base_helpers import deep_get def rule(event): - new = deep_get(event, "details", "new_value", default="") - old = deep_get(event, "details", "old_value", default="") + new = event.deep_get("details", "new_value", default="") + old = event.deep_get("details", "old_value", default="") return all( [ event.get("event_type") == "user_workspace_admin_role_changed", @@ -16,7 +15,7 @@ def rule(event): def title(event): a_c = asana_alert_context(event) - w_s = deep_get(event, "details", "group", "name", default="") + w_s = event.deep_get("details", "group", "name", default="") return ( f"Asana user [{a_c.get('resource_name')}] was made an admin " f"in workspace [{w_s}] by [{a_c.get('actor')}]." diff --git a/rules/asana_rules/asana_workspace_org_export.py b/rules/asana_rules/asana_workspace_org_export.py index 2ac5950e4..83288b37f 100644 --- a/rules/asana_rules/asana_workspace_org_export.py +++ b/rules/asana_rules/asana_workspace_org_export.py @@ -1,11 +1,8 @@ -from panther_base_helpers import deep_get - - def rule(event): return event.get("event_type", "") == "workspace_export_started" def title(event): - actor_email = deep_get(event, "actor", "email", default="") - context_type = deep_get(event, "context", "context_type", default="") + actor_email = event.deep_get("actor", "email", default="") + context_type = event.deep_get("context", "context_type", default="") return f"Asana user [{actor_email}] started a [{context_type}] export for your organization." diff --git a/rules/asana_rules/asana_workspace_password_requirements_simple.py b/rules/asana_rules/asana_workspace_password_requirements_simple.py index bc637dea3..56452a764 100644 --- a/rules/asana_rules/asana_workspace_password_requirements_simple.py +++ b/rules/asana_rules/asana_workspace_password_requirements_simple.py @@ -1,8 +1,5 @@ -from panther_base_helpers import deep_get - - def rule(event): - new_val = deep_get(event, "details", "new_value", default="") + new_val = event.deep_get("details", "new_value", default="") return all( [ event.get("event_type", "") @@ -13,9 +10,9 @@ def rule(event): def title(event): - actor_email = deep_get(event, "actor", "email", default="") - new_value = deep_get(event, "details", "new_value", default="") - old_value = deep_get(event, "details", "old_value", default="") + actor_email = event.deep_get("actor", "email", default="") + new_value = event.deep_get("details", "new_value", default="") + old_value = event.deep_get("details", "old_value", default="") return ( f"Asana user [{actor_email}] changed your organization's password requirements " f"from [{old_value}] to [{new_value}]." diff --git a/rules/asana_rules/asana_workspace_require_app_approvals_disabled.py b/rules/asana_rules/asana_workspace_require_app_approvals_disabled.py index cc3b0c27a..f47eb477f 100644 --- a/rules/asana_rules/asana_workspace_require_app_approvals_disabled.py +++ b/rules/asana_rules/asana_workspace_require_app_approvals_disabled.py @@ -1,8 +1,5 @@ -from panther_base_helpers import deep_get - - def rule(event): - new_val = deep_get(event, "details", "new_value", default="") + new_val = event.deep_get("details", "new_value", default="") return all( [ event.get("event_type", "") @@ -13,8 +10,8 @@ def rule(event): def title(event): - actor_email = deep_get(event, "actor", "email", default="") - context = deep_get(event, "context", "context_type", default="") + actor_email = event.deep_get("actor", "email", default="") + context = event.deep_get("context", "context_type", default="") return ( f"Asana user [{actor_email}] disabled application approval requirements " f"for [{context}] type applications." diff --git a/rules/asana_rules/asana_workspace_saml_optional.py b/rules/asana_rules/asana_workspace_saml_optional.py index 88e5a7e9a..544619267 100644 --- a/rules/asana_rules/asana_workspace_saml_optional.py +++ b/rules/asana_rules/asana_workspace_saml_optional.py @@ -1,9 +1,6 @@ -from panther_base_helpers import deep_get - - def rule(event): - old_val = deep_get(event, "details", "old_value", default="") - new_val = deep_get(event, "details", "new_value", default="") + old_val = event.deep_get("details", "old_value", default="") + new_val = event.deep_get("details", "new_value", default="") return all( [ event.get("event_type", "") == "workspace_saml_settings_changed", @@ -14,5 +11,5 @@ def rule(event): def title(event): - actor_email = deep_get(event, "actor", "email", default="") + actor_email = event.deep_get("actor", "email", default="") return f"Asana user [{actor_email}] made SAML optional for your organization." diff --git a/rules/atlassian_rules/user_logged_in_as_user.py b/rules/atlassian_rules/user_logged_in_as_user.py index 613490f4d..ebefdd580 100644 --- a/rules/atlassian_rules/user_logged_in_as_user.py +++ b/rules/atlassian_rules/user_logged_in_as_user.py @@ -1,25 +1,22 @@ -from panther_base_helpers import deep_get - - def rule(event): return ( - deep_get(event, "attributes", "action", default="") + event.deep_get("attributes", "action", default="") == "user_logged_in_as_user" ) def title(event): - actor = deep_get(event, "attributes", "actor", "email", default="") - context = deep_get(event, "attributes", "context", default=[{}]) + actor = event.deep_get("attributes", "actor", "email", default="") + context = event.deep_get("attributes", "context", default=[{}]) impersonated_user = context[0].get("attributes", {}).get("email", "") return f"{actor} logged in as {impersonated_user}." def alert_context(event): return { - "Timestamp": deep_get(event, "attributes", "time", default=""), - "Actor": deep_get(event, "attributes", "actor", "email", default=""), - "Impersonated user": deep_get(event, "attributes", "context", default=[{}])[0] + "Timestamp": event.deep_get("attributes", "time", default=""), + "Actor": event.deep_get("attributes", "actor", "email", default=""), + "Impersonated user": event.deep_get("attributes", "context", default=[{}])[0] .get("attributes", {}) .get("email", ""), "Event ID": event.get("id"), diff --git a/rules/auth0_rules/auth0_cic_credential_stuffing.py b/rules/auth0_rules/auth0_cic_credential_stuffing.py index 585f787e9..b508246c3 100644 --- a/rules/auth0_rules/auth0_cic_credential_stuffing.py +++ b/rules/auth0_rules/auth0_cic_credential_stuffing.py @@ -16,7 +16,7 @@ def title(event): user = event.deep_get( "data", "details", "request", "auth", "user", "email", default="" ) - p_source_label = event.deep_get("p_source_label", default="") + p_source_label = event.get("p_source_label", "") return ( f"Auth0 User [{user}] had a suspicious [{event_type}] event in " f"your organization's tenant [{p_source_label}]." diff --git a/rules/auth0_rules/auth0_custom_role_created.py b/rules/auth0_rules/auth0_custom_role_created.py index 22804e97a..4befbfa4b 100644 --- a/rules/auth0_rules/auth0_custom_role_created.py +++ b/rules/auth0_rules/auth0_custom_role_created.py @@ -1,12 +1,11 @@ from global_filter_auth0 import filter_include_event from panther_auth0_helpers import auth0_alert_context, is_auth0_config_event -from panther_base_helpers import deep_get def rule(event): if not filter_include_event(event): return False - data_description = deep_get(event, "data", "description", default="") + data_description = event.deep_get("data", "description", default="") return all( [ data_description == "Create a role", @@ -16,14 +15,14 @@ def rule(event): def title(event): - user = deep_get( - event, "data", "details", "request", "auth", "user", "email", default="" + user = event.deep_get( + "data", "details", "request", "auth", "user", "email", default="" ) - request_body_name = deep_get( - event, "data", "details", "request", "body", "name", default="" + request_body_name = event.deep_get( + "data", "details", "request", "body", "name", default="" ) - request_body_description = deep_get( - event, "data", "details", "request", "body", default="" + request_body_description = event.deep_get( + "data", "details", "request", "body", default="" ) if "admin" in request_body_description or "admin" in request_body_name: @@ -31,7 +30,7 @@ def title(event): else: role_type = "custom" - p_source_label = deep_get(event, "p_source_label", default="") + p_source_label = event.get("p_source_label", "") return ( f"Auth0 User [{user}] created a " f"role [{request_body_name}] with [{role_type}] " @@ -40,11 +39,11 @@ def title(event): def severity(event): - request_body_name = deep_get( - event, "data", "details", "request", "body", "name", default="" + request_body_name = event.deep_get( + "data", "details", "request", "body", "name", default="" ) - request_body_description = deep_get( - event, "data", "details", "request", "body", "description", default="" + request_body_description = event.deep_get( + "data", "details", "request", "body", "description", default="" ) if "admin" in request_body_description or "admin" in request_body_name: return "MEDIUM" diff --git a/rules/auth0_rules/auth0_integration_installed.py b/rules/auth0_rules/auth0_integration_installed.py index dad275d3c..bacc26fcd 100644 --- a/rules/auth0_rules/auth0_integration_installed.py +++ b/rules/auth0_rules/auth0_integration_installed.py @@ -1,14 +1,13 @@ from global_filter_auth0 import filter_include_event from panther_auth0_helpers import auth0_alert_context, is_auth0_config_event -from panther_base_helpers import deep_get def rule(event): if not filter_include_event(event): return False - data_description = deep_get(event, "data", "description", default="") - request_path = deep_get( - event, "data", "details", "request", "path", default="" + data_description = event.deep_get("data", "description", default="") + request_path = event.deep_get( + "data", "details", "request", "path", default="" ) return all( [ @@ -20,10 +19,10 @@ def rule(event): def title(event): - user = deep_get( - event, "data", "details", "request", "auth", "user", "email", default="" + user = event.deep_get( + "data", "details", "request", "auth", "user", "email", default="" ) - p_source_label = deep_get(event, "p_source_label", default="") + p_source_label = event.get("p_source_label", "") return ( f"Auth0 User [{user}] installed an integration from the actions library for " f"your organization's tenant [{p_source_label}]." diff --git a/rules/auth0_rules/auth0_mfa_factor_setting_enabled.py b/rules/auth0_rules/auth0_mfa_factor_setting_enabled.py index 8ee031cde..696226f20 100644 --- a/rules/auth0_rules/auth0_mfa_factor_setting_enabled.py +++ b/rules/auth0_rules/auth0_mfa_factor_setting_enabled.py @@ -1,13 +1,12 @@ from global_filter_auth0 import filter_include_event from panther_auth0_helpers import auth0_alert_context, is_auth0_config_event -from panther_base_helpers import deep_get def rule(event): if not filter_include_event(event): return False - description = deep_get(event, "data", "description", default="") - enabled = deep_get(event, "data", "details", "response", "body", "enabled") + description = event.deep_get("data", "description", default="") + enabled = event.deep_get("data", "details", "response", "body", "enabled") return all( [ description == "Update a Multi-factor Authentication Factor", @@ -18,11 +17,11 @@ def rule(event): def title(event): - user = deep_get( - event, "data", "details", "request", "auth", "user", "email", default="" + user = event.deep_get( + "data", "details", "request", "auth", "user", "email", default="" ) - path = deep_get(event, "data", "details", "request", "path", default="") - p_source_label = deep_get(event, "p_source_label", default="") + path = event.deep_get("data", "details", "request", "path", default="") + p_source_label = event.get("p_source_label", "") return ( f"Auth0 User [{user}] enabled mfa factor settings for [{path}] " f"in your organization’s tenant [{p_source_label}]." diff --git a/rules/auth0_rules/auth0_mfa_policy_disabled.py b/rules/auth0_rules/auth0_mfa_policy_disabled.py index ec5cf874d..2bbf31ea2 100644 --- a/rules/auth0_rules/auth0_mfa_policy_disabled.py +++ b/rules/auth0_rules/auth0_mfa_policy_disabled.py @@ -1,16 +1,15 @@ from global_filter_auth0 import filter_include_event from panther_auth0_helpers import auth0_alert_context, is_auth0_config_event -from panther_base_helpers import deep_get def rule(event): if not filter_include_event(event): return False - data_description = deep_get(event, "data", "description", default="") - request_path = deep_get( - event, "data", "details", "request", "path", default="" + data_description = event.deep_get("data", "description", default="") + request_path = event.deep_get( + "data", "details", "request", "path", default="" ) - request_body = deep_get(event, "data", "details", "request", "body", default=[-1]) + request_body = event.deep_get("data", "details", "request", "body", default=[-1]) return all( [ data_description == "Set the Multi-factor Authentication policies", @@ -22,10 +21,10 @@ def rule(event): def title(event): - user = deep_get( - event, "data", "details", "request", "auth", "user", "email", default="" + user = event.deep_get( + "data", "details", "request", "auth", "user", "email", default="" ) - p_source_label = deep_get(event, "p_source_label", default="") + p_source_label = event.get("p_source_label", "") return ( f"Auth0 User [{user}] set mfa requirement settings to 'Never' for your " f"organization's tenant [{p_source_label}]." diff --git a/rules/auth0_rules/auth0_mfa_policy_enabled.py b/rules/auth0_rules/auth0_mfa_policy_enabled.py index 331ebf192..bede1f0f6 100644 --- a/rules/auth0_rules/auth0_mfa_policy_enabled.py +++ b/rules/auth0_rules/auth0_mfa_policy_enabled.py @@ -1,14 +1,13 @@ from global_filter_auth0 import filter_include_event from panther_auth0_helpers import auth0_alert_context, is_auth0_config_event -from panther_base_helpers import deep_get def rule(event): if not filter_include_event(event): return False - data_description = deep_get(event, "data", "description", default="") - request_path = deep_get( - event, "data", "details", "request", "path", default="" + data_description = event.deep_get("data", "description", default="") + request_path = event.deep_get( + "data", "details", "request", "path", default="" ) return all( [ @@ -20,10 +19,10 @@ def rule(event): def title(event): - user_email = deep_get( - event, "data", "details", "request", "auth", "user", "email", default="" + user_email = event.deep_get( + "data", "details", "request", "auth", "user", "email", default="" ) - request_body = deep_get(event, "data", "details", "request", "body", default=[]) + request_body = event.deep_get("data", "details", "request", "body", default=[]) if "all-applications" in request_body: setting_change = "Always Require" diff --git a/rules/auth0_rules/auth0_mfa_risk_assessment_disabled.py b/rules/auth0_rules/auth0_mfa_risk_assessment_disabled.py index 6d26fb2be..8645145c7 100644 --- a/rules/auth0_rules/auth0_mfa_risk_assessment_disabled.py +++ b/rules/auth0_rules/auth0_mfa_risk_assessment_disabled.py @@ -1,17 +1,16 @@ from global_filter_auth0 import filter_include_event from panther_auth0_helpers import auth0_alert_context, is_auth0_config_event -from panther_base_helpers import deep_get def rule(event): if not filter_include_event(event): return False - data_description = deep_get(event, "data", "description", default="") - request_path = deep_get( - event, "data", "details", "request", "path", default="" + data_description = event.deep_get("data", "description", default="") + request_path = event.deep_get( + "data", "details", "request", "path", default="" ) - request_body = deep_get( - event, "data", "details", "request", "body", "AfterAuthentication", default=[] + request_body = event.deep_get( + "data", "details", "request", "body", "AfterAuthentication", default=[] ) return all( [ @@ -24,10 +23,10 @@ def rule(event): def title(event): - user = deep_get( - event, "data", "details", "request", "auth", "user", "email", default="" + user = event.deep_get( + "data", "details", "request", "auth", "user", "email", default="" ) - p_source_label = deep_get(event, "p_source_label", default="") + p_source_label = event.get("p_source_label", "") return ( f"Auth0 User [{user}] disabled mfa risk assessment settings for your " f"organization’s tenant [{p_source_label}]." diff --git a/rules/auth0_rules/auth0_mfa_risk_assessment_enabled.py b/rules/auth0_rules/auth0_mfa_risk_assessment_enabled.py index 857c98eeb..c40403448 100644 --- a/rules/auth0_rules/auth0_mfa_risk_assessment_enabled.py +++ b/rules/auth0_rules/auth0_mfa_risk_assessment_enabled.py @@ -1,17 +1,16 @@ from global_filter_auth0 import filter_include_event from panther_auth0_helpers import auth0_alert_context, is_auth0_config_event -from panther_base_helpers import deep_get def rule(event): if not filter_include_event(event): return False - data_description = deep_get(event, "data", "description", default="") - request_path = deep_get( - event, "data", "details", "request", "path", default="" + data_description = event.deep_get("data", "description", default="") + request_path = event.deep_get( + "data", "details", "request", "path", default="" ) - request_body = deep_get( - event, "data", "details", "request", "body", "AfterAuthentication", default=[] + request_body = event.deep_get( + "data", "details", "request", "body", "AfterAuthentication", default=[] ) return all( [ @@ -24,10 +23,10 @@ def rule(event): def title(event): - user = deep_get( - event, "data", "details", "request", "auth", "user", "email", default="" + user = event.deep_get( + "data", "details", "request", "auth", "user", "email", default="" ) - p_source_label = deep_get(event, "p_source_label", default="") + p_source_label = event.get("p_source_label", "") return ( f"Auth0 User [{user}] enabled mfa risk assessment settings for your " f"organization’s tenant [{p_source_label}]." diff --git a/rules/auth0_rules/auth0_post_login_action_flow.py b/rules/auth0_rules/auth0_post_login_action_flow.py index fb6f02afa..75b61b075 100644 --- a/rules/auth0_rules/auth0_post_login_action_flow.py +++ b/rules/auth0_rules/auth0_post_login_action_flow.py @@ -7,9 +7,9 @@ def rule(event): if not filter_include_event(event): return False - data_description = deep_get(event, "data", "description", default="") - request_path = deep_get( - event, "data", "details", "request", "path", default="" + data_description = event.deep_get("data", "description", default="") + request_path = event.deep_get( + "data", "details", "request", "path", default="" ) return all( @@ -22,13 +22,13 @@ def rule(event): def title(event): - user = deep_get( - event, "data", "details", "request", "auth", "user", "email", default="" + user = event.deep_get( + "data", "details", "request", "auth", "user", "email", default="" ) - p_source_label = deep_get(event, "p_source_label", default="") - request_bindings = deep_get(event, "data", "details", "request", "body", "bindings", default=[]) - response_bindings = deep_get( - event, "data", "details", "response", "body", "bindings", default=[] + p_source_label = event.get("p_source_label", "") + request_bindings = event.deep_get("data", "details", "request", "body", "bindings", default=[]) + response_bindings = event.deep_get( + "data", "details", "response", "body", "bindings", default=[] ) actions_added_list = [] diff --git a/rules/auth0_rules/auth0_user_invitation_created.py b/rules/auth0_rules/auth0_user_invitation_created.py index 12c71a2b4..bdb7c40e0 100644 --- a/rules/auth0_rules/auth0_user_invitation_created.py +++ b/rules/auth0_rules/auth0_user_invitation_created.py @@ -2,7 +2,6 @@ from global_filter_auth0 import filter_include_event from panther_auth0_helpers import auth0_alert_context, is_auth0_config_event -from panther_base_helpers import deep_get org_re = re.compile(r"^/api/v2/organizations/[^/\s]+/invitations$") @@ -18,23 +17,23 @@ def title(event): inv_type = invitation_type(event) if inv_type == "tenant": try: - invitee = deep_get(event, "data", "details", "request", "body", "owners", default=[])[0] + invitee = event.deep_get("data", "details", "request", "body", "owners", default=[])[0] except IndexError: invitee = "" elif inv_type == "organization": - invitee = deep_get(event, "data", "details", "request", "body", "invitee", "email") + invitee = event.deep_get("data", "details", "request", "body", "invitee", "email") else: invitee = "" - inviter = deep_get( - event, "data", "details", "request", "auth", "user", "email", default="" + inviter = event.deep_get( + "data", "details", "request", "auth", "user", "email", default="" ) - source = deep_get(event, "p_source_label", default="") + source = event.get("p_source_label", "") return f"Auth0 User [{inviter}] invited [{invitee}] to {inv_type} [{source}]]" def invitation_type(event): - path = deep_get(event, "data", "details", "request", "path", default="") + path = event.deep_get("data", "details", "request", "path", default="") if path == "/api/v2/tenants/invitations": return "tenant" diff --git a/rules/auth0_rules/auth0_user_joined_tenant.py b/rules/auth0_rules/auth0_user_joined_tenant.py index 99a5fcfd5..4926889a4 100644 --- a/rules/auth0_rules/auth0_user_joined_tenant.py +++ b/rules/auth0_rules/auth0_user_joined_tenant.py @@ -1,14 +1,12 @@ from global_filter_auth0 import filter_include_event from panther_auth0_helpers import auth0_alert_context, is_auth0_config_event -from panther_base_helpers import deep_get def rule(event): if not filter_include_event(event): return False - data_description = deep_get(event, "data", "description", default="") - scopes = deep_get( - event, + data_description = event.deep_get("data", "description", default="") + scopes = event.deep_get( "data", "details", "request", @@ -17,7 +15,7 @@ def rule(event): "scopes", default=[""], ) - state = deep_get(event, "data", "details", "request", "body", "state", default="") + state = event.deep_get("data", "details", "request", "body", "state", default="") return all( [ data_description == "Update an invitation", @@ -29,10 +27,10 @@ def rule(event): def title(event): - user = deep_get( - event, "data", "details", "request", "auth", "user", "email", default="" + user = event.deep_get( + "data", "details", "request", "auth", "user", "email", default="" ) - p_source_label = deep_get(event, "p_source_label", default="") + p_source_label = event.get("p_source_label", "") return ( f"Auth0 User [{user}] has accepted an invitation to join your " f"organization's tenant [{p_source_label}]." diff --git a/rules/aws_cloudtrail_rules/aws_ami_modified_for_public_access.py b/rules/aws_cloudtrail_rules/aws_ami_modified_for_public_access.py index 819a87e49..cfca5857e 100644 --- a/rules/aws_cloudtrail_rules/aws_ami_modified_for_public_access.py +++ b/rules/aws_cloudtrail_rules/aws_ami_modified_for_public_access.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context from panther_default import aws_cloudtrail_success @@ -7,8 +7,8 @@ def rule(event): if not aws_cloudtrail_success(event) or event.get("eventName") != "ModifyImageAttribute": return False - added_perms = deep_get( - event, "requestParameters", "launchPermission", "add", "items", default=[] + added_perms = event.deep_get( + "requestParameters", "launchPermission", "add", "items", default=[] ) for item in added_perms: diff --git a/rules/aws_cloudtrail_rules/aws_cloudtrail_account_discovery.py b/rules/aws_cloudtrail_rules/aws_cloudtrail_account_discovery.py index 85ea25cec..ec31115f6 100644 --- a/rules/aws_cloudtrail_rules/aws_cloudtrail_account_discovery.py +++ b/rules/aws_cloudtrail_rules/aws_cloudtrail_account_discovery.py @@ -1,5 +1,3 @@ -from panther_base_helpers import deep_get - DISCOVERY_EVENTS = [ "GetAlternateContact", "GetContactInformation", @@ -15,7 +13,7 @@ def rule(event): def title(event): return ( - f"User [{deep_get(event, 'userIdentity', 'arn')}]" + f"User [{event.deep_get('userIdentity', 'arn')}]" f"performed a [{event.get('eventName')}] " f"action in AWS account [{event.get('recipientAccountId')}]." ) diff --git a/rules/aws_cloudtrail_rules/aws_cloudtrail_created.py b/rules/aws_cloudtrail_rules/aws_cloudtrail_created.py index a3d54f291..7f6694839 100644 --- a/rules/aws_cloudtrail_rules/aws_cloudtrail_created.py +++ b/rules/aws_cloudtrail_rules/aws_cloudtrail_created.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context from panther_default import aws_cloudtrail_success # API calls that are indicative of CloudTrail changes @@ -14,7 +14,7 @@ def rule(event): def title(event): - return f"CloudTrail [{deep_get(event, 'requestParameters', 'name')}] was created/updated" + return f"CloudTrail [{event.deep_get('requestParameters', 'name')}] was created/updated" def alert_context(event): diff --git a/rules/aws_cloudtrail_rules/aws_cloudtrail_loginprofilecreatedormodified.py b/rules/aws_cloudtrail_rules/aws_cloudtrail_loginprofilecreatedormodified.py index 4d376261d..e6e01a70c 100644 --- a/rules/aws_cloudtrail_rules/aws_cloudtrail_loginprofilecreatedormodified.py +++ b/rules/aws_cloudtrail_rules/aws_cloudtrail_loginprofilecreatedormodified.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context PROFILE_EVENTS = { "UpdateLoginProfile", @@ -15,17 +15,17 @@ def rule(event): return ( event.get("eventSource", "") == "iam.amazonaws.com" and event.get("eventName", "") in PROFILE_EVENTS - and not deep_get(event, "userIdentity", "arn", default="").endswith( - f"/{deep_get(event, 'requestParameters', 'userName', default='')}" + and not event.deep_get("userIdentity", "arn", default="").endswith( + f"/{event.deep_get('requestParameters', 'userName', default='')}" ) ) def title(event): return ( - f"[{deep_get(event, 'userIdentity', 'arn')}] " + f"[{event.deep_get('userIdentity', 'arn')}] " f"changed the password for " - f"[{deep_get(event, 'requestParameters','userName')}]" + f"[{event.deep_get('requestParameters','userName')}]" ) diff --git a/rules/aws_cloudtrail_rules/aws_cloudtrail_password_policy_discovery.py b/rules/aws_cloudtrail_rules/aws_cloudtrail_password_policy_discovery.py index 81d7d113e..da144c0aa 100644 --- a/rules/aws_cloudtrail_rules/aws_cloudtrail_password_policy_discovery.py +++ b/rules/aws_cloudtrail_rules/aws_cloudtrail_password_policy_discovery.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context PASSWORD_DISCOVERY_EVENTS = [ "GetAccountPasswordPolicy", @@ -13,7 +13,7 @@ def rule(event): def title(event): - user_arn = deep_get(event, "useridentity", "arn", default="") + user_arn = event.deep_get("useridentity", "arn", default="") return f"Password Policy Discovery detected in AWS CloudTrail from [{user_arn}]" diff --git a/rules/aws_cloudtrail_rules/aws_cloudtrail_stopped.py b/rules/aws_cloudtrail_rules/aws_cloudtrail_stopped.py index e734d42a1..468a7298f 100644 --- a/rules/aws_cloudtrail_rules/aws_cloudtrail_stopped.py +++ b/rules/aws_cloudtrail_rules/aws_cloudtrail_stopped.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context from panther_default import aws_cloudtrail_success, lookup_aws_account_name # API calls that are indicative of CloudTrail changes @@ -14,7 +14,7 @@ def rule(event): def dedup(event): # Merge on the CloudTrail ARN - return deep_get(event, "requestParameters", "name", default="") + return event.deep_get("requestParameters", "name", default="") def title(event): diff --git a/rules/aws_cloudtrail_rules/aws_cloudtrail_unsuccessful_mfa_attempt.py b/rules/aws_cloudtrail_rules/aws_cloudtrail_unsuccessful_mfa_attempt.py index bc83c4876..2ef367fd6 100644 --- a/rules/aws_cloudtrail_rules/aws_cloudtrail_unsuccessful_mfa_attempt.py +++ b/rules/aws_cloudtrail_rules/aws_cloudtrail_unsuccessful_mfa_attempt.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context def rule(event): @@ -8,8 +8,8 @@ def rule(event): ): return False - mfa_used = deep_get(event, "additionalEventData", "MFAUsed", default="") - console_login = deep_get(event, "responseElements", "ConsoleLogin", default="") + mfa_used = event.deep_get("additionalEventData", "MFAUsed", default="") + console_login = event.deep_get("responseElements", "ConsoleLogin", default="") if mfa_used == "Yes" and console_login == "Failure": return True @@ -17,8 +17,8 @@ def rule(event): def title(event): - arn = deep_get(event, "userIdenity", "arn", default="No ARN") - username = deep_get(event, "userIdentity", "userName", default="No Username") + arn = event.deep_get("userIdenity", "arn", default="No ARN") + username = event.deep_get("userIdentity", "userName", default="No Username") return f"Failed MFA login from [{arn}] [{username}]" diff --git a/rules/aws_cloudtrail_rules/aws_cloudtrail_useraccesskeyauth.py b/rules/aws_cloudtrail_rules/aws_cloudtrail_useraccesskeyauth.py index 335f6f184..30f9e3c7b 100644 --- a/rules/aws_cloudtrail_rules/aws_cloudtrail_useraccesskeyauth.py +++ b/rules/aws_cloudtrail_rules/aws_cloudtrail_useraccesskeyauth.py @@ -14,7 +14,7 @@ def title(event): def alert_context(event): return { - "ip_accessKeyId": event.get("sourceIpAddress", default="{not found}") + "ip_accessKeyId": event.get("sourceIpAddress", "{not found}") + ":" + event.deep_get("userIdentity", "accessKeyId", default="{not found}") } diff --git a/rules/aws_cloudtrail_rules/aws_codebuild_made_public.py b/rules/aws_cloudtrail_rules/aws_codebuild_made_public.py index 6c3b0616c..49b8b90f3 100644 --- a/rules/aws_cloudtrail_rules/aws_codebuild_made_public.py +++ b/rules/aws_cloudtrail_rules/aws_codebuild_made_public.py @@ -1,18 +1,18 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context from panther_default import lookup_aws_account_name def rule(event): return ( event["eventName"] == "UpdateProjectVisibility" - and deep_get(event, "requestParameters", "projectVisibility") == "PUBLIC_READ" + and event.deep_get("requestParameters", "projectVisibility") == "PUBLIC_READ" ) def title(event): return ( - f"AWS CodeBuild Project made Public by {deep_get(event, 'userIdentity', 'arn')} " - f"in account {lookup_aws_account_name(deep_get(event, 'recipientAccountId'))}" + f"AWS CodeBuild Project made Public by {event.deep_get('userIdentity', 'arn')} " + f"in account {lookup_aws_account_name(event.deep_get('recipientAccountId'))}" ) diff --git a/rules/aws_cloudtrail_rules/aws_console_login_without_mfa.py b/rules/aws_cloudtrail_rules/aws_console_login_without_mfa.py index 9524e5e51..d73e99168 100644 --- a/rules/aws_cloudtrail_rules/aws_console_login_without_mfa.py +++ b/rules/aws_cloudtrail_rules/aws_console_login_without_mfa.py @@ -1,6 +1,6 @@ import logging -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context from panther_default import lookup_aws_account_name from panther_detection_helpers.caching import check_account_age @@ -16,7 +16,7 @@ def rule(event): # Extract some nested JSON structure additional_event_data = event.get("additionalEventData", {}) response_elements = event.get("responseElements", {}) - user_identity_type = deep_get(event, "userIdentity", "type", default="") + user_identity_type = event.deep_get("userIdentity", "type", default="") # When there is an external IdP setup and users directly assume roles # the additionalData.MFAUsed attribute will be set to "no" @@ -32,7 +32,7 @@ def rule(event): # If using AWS SSOv2 or other SAML provider return False if ( - "AWSReservedSSO" in deep_get(event, "userIdentity", "arn", default=" ") + "AWSReservedSSO" in event.deep_get("userIdentity", "arn", default=" ") or additional_event_data.get("SamlProviderArn") is not None ): return False @@ -41,9 +41,9 @@ def rule(event): # This functionality is not enabled by default, in order to start logging new user creations # Enable indicator_creation_rules/new_account_logging to start logging new users new_user_string = ( - deep_get(event, "userIdentity", "userName", default="") + event.deep_get("userIdentity", "userName", default="") + "-" - + deep_get(event, "userIdentity", "principalId", default="") + + event.deep_get("userIdentity", "principalId", default="") ) is_new_user = check_account_age(new_user_string) if isinstance(is_new_user, str): @@ -71,7 +71,7 @@ def rule(event): # It is not recommended to remove this 'double negative" if ( additional_event_data.get("MFAUsed") != "Yes" - and deep_get(event, "userIdentity", "sessionContext", "attributes", "mfaAuthenticated") + and event.deep_get("userIdentity", "sessionContext", "attributes", "mfaAuthenticated") != "true" ): return True @@ -79,14 +79,14 @@ def rule(event): def title(event): - if deep_get(event, "userIdentity", "type") == "Root": + if event.deep_get("userIdentity", "type") == "Root": user_string = "the root user" else: - user = deep_get(event, "userIdentity", "userName") or deep_get( - event, "userIdentity", "sessionContext", "sessionIssuer", "userName" + user = event.deep_get("userIdentity", "userName") or event.deep_get( + "userIdentity", "sessionContext", "sessionIssuer", "userName" ) - type_ = deep_get( - event, "userIdentity", "sessionContext", "sessionIssuer", "type", default="user" + type_ = event.deep_get( + "userIdentity", "sessionContext", "sessionIssuer", "type", default="user" ).lower() user_string = f"{type_} {user}" account_id = event.get("recipientAccountId") diff --git a/rules/aws_cloudtrail_rules/aws_console_login_without_saml.py b/rules/aws_cloudtrail_rules/aws_console_login_without_saml.py index b4b421079..a8761353d 100644 --- a/rules/aws_cloudtrail_rules/aws_console_login_without_saml.py +++ b/rules/aws_cloudtrail_rules/aws_console_login_without_saml.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context from panther_default import lookup_aws_account_name @@ -6,7 +6,7 @@ def rule(event): additional_event_data = event.get("additionalEventData", {}) return ( event.get("eventName") == "ConsoleLogin" - and deep_get(event, "userIdentity", "type") != "AssumedRole" + and event.deep_get("userIdentity", "type") != "AssumedRole" and not additional_event_data.get("SamlProviderArn") ) diff --git a/rules/aws_cloudtrail_rules/aws_console_root_login.py b/rules/aws_cloudtrail_rules/aws_console_root_login.py index 8e99877e1..3b76e34bb 100644 --- a/rules/aws_cloudtrail_rules/aws_console_root_login.py +++ b/rules/aws_cloudtrail_rules/aws_console_root_login.py @@ -1,4 +1,3 @@ -from panther_base_helpers import deep_get from panther_default import lookup_aws_account_name from panther_oss_helpers import geoinfo_from_ip_formatted @@ -6,8 +5,8 @@ def rule(event): return ( event.get("eventName") == "ConsoleLogin" - and deep_get(event, "userIdentity", "type") == "Root" - and deep_get(event, "responseElements", "ConsoleLogin") == "Success" + and event.deep_get("userIdentity", "type") == "Root" + and event.deep_get("responseElements", "ConsoleLogin") == "Success" ) @@ -31,8 +30,8 @@ def dedup(event): def alert_context(event): return { "sourceIPAddress": event.get("sourceIPAddress"), - "userIdentityAccountId": deep_get(event, "userIdentity", "accountId"), - "userIdentityArn": deep_get(event, "userIdentity", "arn"), + "userIdentityAccountId": event.deep_get("userIdentity", "accountId"), + "userIdentityArn": event.deep_get("userIdentity", "arn"), "eventTime": event.get("eventTime"), - "mfaUsed": deep_get(event, "additionalEventData", "MFAUsed"), + "mfaUsed": event.deep_get("additionalEventData", "MFAUsed"), } diff --git a/rules/aws_cloudtrail_rules/aws_console_root_login_failed.py b/rules/aws_cloudtrail_rules/aws_console_root_login_failed.py index 513f0d43d..030353307 100644 --- a/rules/aws_cloudtrail_rules/aws_console_root_login_failed.py +++ b/rules/aws_cloudtrail_rules/aws_console_root_login_failed.py @@ -1,12 +1,12 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context from panther_default import lookup_aws_account_name def rule(event): return ( event.get("eventName") == "ConsoleLogin" - and deep_get(event, "userIdentity", "type") == "Root" - and deep_get(event, "responseElements", "ConsoleLogin") == "Failure" + and event.deep_get("userIdentity", "type") == "Root" + and event.deep_get("responseElements", "ConsoleLogin") == "Failure" ) diff --git a/rules/aws_cloudtrail_rules/aws_ec2_manual_security_group_changes.py b/rules/aws_cloudtrail_rules/aws_ec2_manual_security_group_changes.py index 021d98ca3..5deda785b 100644 --- a/rules/aws_cloudtrail_rules/aws_ec2_manual_security_group_changes.py +++ b/rules/aws_cloudtrail_rules/aws_ec2_manual_security_group_changes.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get, pattern_match_list +from panther_base_helpers import aws_rule_context, pattern_match_list from panther_default import aws_cloudtrail_success PROD_ACCOUNT_IDS = {"11111111111111", "112233445566"} @@ -37,24 +37,24 @@ def rule(event): pattern_match_list(event.get("userAgent"), ALLOWED_USER_AGENTS) and # Validate the IAM Role used is in our acceptable list - any(role in deep_get(event, "userIdentity", "arn") for role in ALLOWED_ROLE_NAMES) + any(role in event.deep_get("userIdentity", "arn") for role in ALLOWED_ROLE_NAMES) ) ) def dedup(event): return ":".join( - deep_get(event, "requestParameters", field, default="") + event.deep_get("requestParameters", field, default="") for field in SG_CHANGE_EVENTS[event.get("eventName")]["fields"] ) def title(event): title_fields = { - field: deep_get(event, "requestParameters", field, default="") + field: event.deep_get("requestParameters", field, default="") for field in SG_CHANGE_EVENTS[event.get("eventName")]["fields"] } - user = deep_get(event, "userIdentity", "arn", default="").split("/")[-1] + user = event.deep_get("userIdentity", "arn", default="").split("/")[-1] title_template = SG_CHANGE_EVENTS[event.get("eventName")]["title"] title_fields["actor"] = user return title_template.format(**title_fields) diff --git a/rules/aws_cloudtrail_rules/aws_ec2_monitoring.py b/rules/aws_cloudtrail_rules/aws_ec2_monitoring.py index 3f41c6af8..b50be7120 100644 --- a/rules/aws_cloudtrail_rules/aws_ec2_monitoring.py +++ b/rules/aws_cloudtrail_rules/aws_ec2_monitoring.py @@ -1,5 +1,3 @@ -from panther_base_helpers import deep_get - # AWS CloudTrail API eventNames for EC2 Image Actions EC2_IMAGE_ACTIONS = [ "CopyFpgaImage", @@ -24,9 +22,9 @@ def rule(event): # though their userIdentity will be more normal. # Example cloudtrail event in the "Terminate instance From WebUI with assumedRole" test event.get("sourceIPAddress", "").endswith(".amazonaws.com") - or deep_get(event, "userIdentity", "type", default="") == "AWSService" - or deep_get(event, "userIdentity", "invokedBy", default="") == "AWS Internal" - or deep_get(event, "userIdentity", "invokedBy", default="").endswith(".amazonaws.com") + or event.deep_get("userIdentity", "type", default="") == "AWSService" + or event.deep_get("userIdentity", "invokedBy", default="") == "AWS Internal" + or event.deep_get("userIdentity", "invokedBy", default="").endswith(".amazonaws.com") ): return False # Dry run operations get logged as SES Internal in the sourceIPAddress @@ -43,7 +41,7 @@ def rule(event): def title(event): return ( - f"[{deep_get(event, 'userIdentity', 'sessionContext', 'sessionIssuer', 'userName')}] " + f"[{event.deep_get('userIdentity', 'sessionContext', 'sessionIssuer', 'userName')}] " f"triggered a CloudTrail action [{event.get('eventName')}] " f"within AWS Account ID: [{event.get('recipientAccountId')}]" ) diff --git a/rules/aws_cloudtrail_rules/aws_ec2_startup_script_change.py b/rules/aws_cloudtrail_rules/aws_ec2_startup_script_change.py index b41b045d9..fd74dbd82 100644 --- a/rules/aws_cloudtrail_rules/aws_ec2_startup_script_change.py +++ b/rules/aws_cloudtrail_rules/aws_ec2_startup_script_change.py @@ -1,9 +1,9 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context def rule(event): - if event.get("eventName") == "ModifyInstanceAttribute" and deep_get( - event, "requestParameters", "userData" + if event.get("eventName") == "ModifyInstanceAttribute" and event.deep_get( + "requestParameters", "userData" ): return True return False @@ -11,18 +11,18 @@ def rule(event): def title(event): return ( - f"[{deep_get(event,'userIdentity','arn')}] " + f"[{event.deep_get('userIdentity','arn')}] " "modified the startup script for " - f" [{deep_get(event, 'requestParameters', 'instanceId')}] " + f" [{event.deep_get('requestParameters', 'instanceId')}] " f"in [{event.get('recipientAccountId')}] - [{event.get('awsRegion')}]" ) def dedup(event): - return deep_get(event, "requestParameters", "instanceId") + return event.deep_get("requestParameters", "instanceId") def alert_context(event): context = aws_rule_context(event) - context["instance_ids"] = [deep_get(event, "requestParameters", "instanceId"), "no_instance_id"] + context["instance_ids"] = [event.deep_get("requestParameters", "instanceId"), "no_instance_id"] return context diff --git a/rules/aws_cloudtrail_rules/aws_ec2_traffic_mirroring.py b/rules/aws_cloudtrail_rules/aws_ec2_traffic_mirroring.py index 564261c2f..44c84a59b 100644 --- a/rules/aws_cloudtrail_rules/aws_ec2_traffic_mirroring.py +++ b/rules/aws_cloudtrail_rules/aws_ec2_traffic_mirroring.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context def rule(event): @@ -19,7 +19,7 @@ def rule(event): "ModifyTrafficMirrorFilterRule", "ModifyTrafficMirrorSession", ] - if deep_get(event, "userIdentity", "invokedBy", default="").endswith(".amazonaws.com"): + if event.deep_get("userIdentity", "invokedBy", default="").endswith(".amazonaws.com"): return False return ( event.get("eventSource", "") == "ec2.amazonaws.com" diff --git a/rules/aws_cloudtrail_rules/aws_ecr_crud.py b/rules/aws_cloudtrail_rules/aws_ecr_crud.py index efe79e20b..f5b7c6a81 100644 --- a/rules/aws_cloudtrail_rules/aws_ecr_crud.py +++ b/rules/aws_cloudtrail_rules/aws_ecr_crud.py @@ -1,6 +1,6 @@ from fnmatch import fnmatch -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context ECR_CRUD_EVENTS = { "BatchCheckLayerAvailability", @@ -30,7 +30,7 @@ def rule(event): and event.get("eventName") in ECR_CRUD_EVENTS ): for role in ALLOWED_ROLES: - if fnmatch(deep_get(event, "userIdentity", "arn", default="unknown-arn"), role): + if fnmatch(event.deep_get("userIdentity", "arn", default="unknown-arn"), role): return False return True @@ -39,14 +39,14 @@ def rule(event): def title(event): return ( - f"[{deep_get(event, 'userIdentity','arn', default = 'unknown-arn')}] " + f"[{event.deep_get('userIdentity','arn', default = 'unknown-arn')}] " f"performed ECR {event.get('eventName')} in " f"[{event.get('recipientAccountId')} {event.get('awsRegion')}]." ) def dedup(event): - return f"{deep_get(event, 'userIdentity','arn', default = 'unknown-arn')}" + return f"{event.deep_get('userIdentity','arn', default = 'unknown-arn')}" def alert_context(event): diff --git a/rules/aws_cloudtrail_rules/aws_ecr_events.py b/rules/aws_cloudtrail_rules/aws_ecr_events.py index 5d0c91690..2932d2c70 100644 --- a/rules/aws_cloudtrail_rules/aws_ecr_events.py +++ b/rules/aws_cloudtrail_rules/aws_ecr_events.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context # CONFIGURATION REQUIRED: Update with your expected AWS Accounts/Regions AWS_ACCOUNTS_AND_REGIONS = { @@ -9,7 +9,7 @@ def rule(event): if event.get("eventSource") == "ecr.amazonaws.com": - aws_account_id = deep_get(event, "userIdentity", "accountId") + aws_account_id = event.deep_get("userIdentity", "accountId") if aws_account_id in AWS_ACCOUNTS_AND_REGIONS: if event.get("awsRegion") not in AWS_ACCOUNTS_AND_REGIONS[aws_account_id]: return True diff --git a/rules/aws_cloudtrail_rules/aws_iam_assume_role_blocklist_ignored.py b/rules/aws_cloudtrail_rules/aws_iam_assume_role_blocklist_ignored.py index 4e14547a7..ccb2a25c9 100644 --- a/rules/aws_cloudtrail_rules/aws_iam_assume_role_blocklist_ignored.py +++ b/rules/aws_cloudtrail_rules/aws_iam_assume_role_blocklist_ignored.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context from panther_default import aws_cloudtrail_success # This is a list of role ARNs that should not be assumed by users in normal operations @@ -13,10 +13,10 @@ def rule(event): return False # Only considering user actions - if deep_get(event, "userIdentity", "type") not in ["IAMUser", "FederatedUser"]: + if event.deep_get("userIdentity", "type") not in ["IAMUser", "FederatedUser"]: return False - return deep_get(event, "requestParameters", "roleArn") in ASSUME_ROLE_BLOCKLIST + return event.deep_get("requestParameters", "roleArn") in ASSUME_ROLE_BLOCKLIST def alert_context(event): diff --git a/rules/aws_cloudtrail_rules/aws_iam_compromised_key_quarantine.py b/rules/aws_cloudtrail_rules/aws_iam_compromised_key_quarantine.py index 74f8b92ff..49bf8e4ac 100644 --- a/rules/aws_cloudtrail_rules/aws_iam_compromised_key_quarantine.py +++ b/rules/aws_cloudtrail_rules/aws_iam_compromised_key_quarantine.py @@ -19,6 +19,6 @@ def rule(event): def title(event): - account_id = event.deep_get("recipientAccountId", default="") + account_id = event.get("recipientAccountId", "") user_name = event.deep_get("requestParameters", "userName", default="") return f"Compromised Key quarantined for [{user_name}] in AWS Account [{account_id}]" diff --git a/rules/aws_cloudtrail_rules/aws_iam_entity_created_without_cloudformation.py b/rules/aws_cloudtrail_rules/aws_iam_entity_created_without_cloudformation.py index 82546404c..91f6db9ee 100644 --- a/rules/aws_cloudtrail_rules/aws_iam_entity_created_without_cloudformation.py +++ b/rules/aws_cloudtrail_rules/aws_iam_entity_created_without_cloudformation.py @@ -1,6 +1,6 @@ import re -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context from panther_default import aws_cloudtrail_success # The role dedicated for IAM administration @@ -33,7 +33,7 @@ def rule(event): return False # All IAM changes MUST go through CloudFormation - if deep_get(event, "userIdentity", "invokedBy") != "cloudformation.amazonaws.com": + if event.deep_get("userIdentity", "invokedBy") != "cloudformation.amazonaws.com": return True # Only approved IAM Roles can make IAM Changes @@ -43,7 +43,7 @@ def rule(event): len( re.findall( admin_role_pattern, - deep_get(event, "userIdentity", "sessionContext", "sessionIssuer", "arn"), + event.deep_get("userIdentity", "sessionContext", "sessionIssuer", "arn"), ) ) > 0 @@ -51,7 +51,7 @@ def rule(event): return False return ( - deep_get(event, "userIdentity", "sessionContext", "sessionIssuer", "arn") + event.deep_get("userIdentity", "sessionContext", "sessionIssuer", "arn") not in IAM_ADMIN_ROLES ) diff --git a/rules/aws_cloudtrail_rules/aws_iam_user_key_created.py b/rules/aws_cloudtrail_rules/aws_iam_user_key_created.py index d6b82a905..afeaf695d 100644 --- a/rules/aws_cloudtrail_rules/aws_iam_user_key_created.py +++ b/rules/aws_cloudtrail_rules/aws_iam_user_key_created.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context from panther_default import aws_cloudtrail_success @@ -8,8 +8,8 @@ def rule(event): and event.get("eventSource") == "iam.amazonaws.com" and event.get("eventName") == "CreateAccessKey" and ( - not deep_get(event, "userIdentity", "arn", default="").endswith( - f"user/{deep_get(event, 'responseElements', 'accessKey', 'userName', default='')}" + not event.deep_get("userIdentity", "arn", default="").endswith( + f"user/{event.deep_get('responseElements', 'accessKey', 'userName', default='')}" ) ) ) @@ -17,14 +17,14 @@ def rule(event): def title(event): return ( - f"[{deep_get(event,'userIdentity','arn')}]" + f"[{event.deep_get('userIdentity','arn')}]" " created API keys for " - f"[{deep_get(event,'responseElements','accessKey','userName', default = '')}]" + f"[{event.deep_get('responseElements','accessKey','userName', default = '')}]" ) def dedup(event): - return f"{deep_get(event,'userIdentity','arn')}" + return f"{event.deep_get('userIdentity','arn')}" def alert_context(event): diff --git a/rules/aws_cloudtrail_rules/aws_iam_user_recon_denied.py b/rules/aws_cloudtrail_rules/aws_iam_user_recon_denied.py index 27563c7b1..77ffe49b1 100644 --- a/rules/aws_cloudtrail_rules/aws_iam_user_recon_denied.py +++ b/rules/aws_cloudtrail_rules/aws_iam_user_recon_denied.py @@ -1,6 +1,6 @@ from ipaddress import ip_address -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context from panther_default import lookup_aws_account_name # service/event patterns to monitor @@ -17,7 +17,7 @@ def rule(event): # Filter events if event.get("errorCode") != "AccessDenied": return False - if deep_get(event, "userIdentity", "type") != "IAMUser": + if event.deep_get("userIdentity", "type") != "IAMUser": return False # Console Activity can easily result in false positives as some pages contain a mix of @@ -41,13 +41,13 @@ def rule(event): def dedup(event): - return deep_get(event, "userIdentity", "arn") + return event.deep_get("userIdentity", "arn") def title(event): - user_type = deep_get(event, "userIdentity", "type") + user_type = event.deep_get("userIdentity", "type") if user_type == "IAMUser": - user = deep_get(event, "userIdentity", "userName") + user = event.deep_get("userIdentity", "userName") # root user elif user_type == "Root": user = user_type diff --git a/rules/aws_cloudtrail_rules/aws_key_compromised.py b/rules/aws_cloudtrail_rules/aws_key_compromised.py index 72100aa88..04035f957 100644 --- a/rules/aws_cloudtrail_rules/aws_key_compromised.py +++ b/rules/aws_cloudtrail_rules/aws_key_compromised.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context EXPOSED_CRED_POLICY = "AWSExposedCredentialPolicy_DO_NOT_REMOVE" @@ -14,12 +14,12 @@ def rule(event): def dedup(event): - return deep_get(event, "userIdentity", "userName") + return event.deep_get("userIdentity", "userName") def title(event): return ( - f"{dedup(event)}'s access key ID [{deep_get(event, 'userIdentity', 'accessKeyId')}]" + f"{dedup(event)}'s access key ID [{event.deep_get('userIdentity', 'accessKeyId')}]" f" was uploaded to a public GitHub repo" ) diff --git a/rules/aws_cloudtrail_rules/aws_lambda_crud.py b/rules/aws_cloudtrail_rules/aws_lambda_crud.py index 8d67c3886..3a5f5ab10 100644 --- a/rules/aws_cloudtrail_rules/aws_lambda_crud.py +++ b/rules/aws_cloudtrail_rules/aws_lambda_crud.py @@ -1,6 +1,6 @@ from fnmatch import fnmatch -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context LAMBDA_CRUD_EVENTS = { "AddPermission", @@ -29,7 +29,7 @@ def rule(event): and event.get("eventName") in LAMBDA_CRUD_EVENTS ): for role in ALLOWED_ROLES: - if fnmatch(deep_get(event, "userIdentity", "arn", default="unknown-arn"), role): + if fnmatch(event.deep_get("userIdentity", "arn", default="unknown-arn"), role): return False return True return False @@ -37,7 +37,7 @@ def rule(event): def title(event): return ( - f"[{deep_get(event, 'userIdentity','arn', default = 'unknown-arn')}] " + f"[{event.deep_get('userIdentity','arn', default = 'unknown-arn')}] " f"performed Lambda " f"[{event.get('eventName')}] in " f"[{event.get('recipientAccountId')} {event.get('awsRegion')}]." @@ -45,7 +45,7 @@ def title(event): def dedup(event): - return f"{deep_get(event, 'userIdentity','arn', default = 'unknown-arn')}" + return f"{event.deep_get('userIdentity','arn', default = 'unknown-arn')}" def alert_context(event): diff --git a/rules/aws_cloudtrail_rules/aws_macie_evasion.py b/rules/aws_cloudtrail_rules/aws_macie_evasion.py index b412dce05..b3cfa02a2 100644 --- a/rules/aws_cloudtrail_rules/aws_macie_evasion.py +++ b/rules/aws_cloudtrail_rules/aws_macie_evasion.py @@ -1,4 +1,4 @@ -from panther_base_helpers import deep_get, pattern_match +from panther_base_helpers import pattern_match MACIE_EVENTS = { "ArchiveFindings", @@ -23,5 +23,5 @@ def rule(event): def title(event): account = event.get("recipientAccountId") - user_arn = deep_get(event, "userIdentity", "arn") + user_arn = event.deep_get("userIdentity", "arn") return f"AWS Macie in AWS Account [{account}] Disabled/Updated by [{user_arn}]" diff --git a/rules/aws_cloudtrail_rules/aws_modify_cloud_compute_infrastructure.py b/rules/aws_cloudtrail_rules/aws_modify_cloud_compute_infrastructure.py index eea7e3a1d..106fc3d06 100644 --- a/rules/aws_cloudtrail_rules/aws_modify_cloud_compute_infrastructure.py +++ b/rules/aws_cloudtrail_rules/aws_modify_cloud_compute_infrastructure.py @@ -1,5 +1,3 @@ -from panther_base_helpers import deep_get - EC2_CRUD_ACTIONS = { "AssociateIamInstanceProfile", "AssociateInstanceEventWindow", @@ -47,9 +45,9 @@ def rule(event): # though their userIdentity will be more normal. # Example cloudtrail event in the "Terminate instance From WebUI with assumedRole" test event.get("sourceIPAddress", "").endswith(".amazonaws.com") - or deep_get(event, "userIdentity", "type", default="") == "AWSService" - or deep_get(event, "userIdentity", "invokedBy", default="") == "AWS Internal" - or deep_get(event, "userIdentity", "invokedBy", default="").endswith(".amazonaws.com") + or event.deep_get("userIdentity", "type", default="") == "AWSService" + or event.deep_get("userIdentity", "invokedBy", default="") == "AWS Internal" + or event.deep_get("userIdentity", "invokedBy", default="").endswith(".amazonaws.com") ): return False # Dry run operations get logged as SES Internal in the sourceIPAddress @@ -64,8 +62,8 @@ def rule(event): def title(event): - items = deep_get( - event, "requestParameters", "instancesSet", "items", default=[{"instanceId": "none"}] + items = event.deep_get( + "requestParameters", "instancesSet", "items", default=[{"instanceId": "none"}] ) return ( f"AWS Event [{event.get('eventName')}] Instance ID " @@ -74,8 +72,8 @@ def title(event): def alert_context(event): - items = deep_get( - event, "requestParameters", "instancesSet", "items", default=[{"instanceId": "none"}] + items = event.deep_get( + "requestParameters", "instancesSet", "items", default=[{"instanceId": "none"}] ) return { "awsRegion": event.get("awsRegion"), diff --git a/rules/aws_cloudtrail_rules/aws_network_acl_permissive_entry.py b/rules/aws_cloudtrail_rules/aws_network_acl_permissive_entry.py index 55e82c6ae..9ff9b29c0 100644 --- a/rules/aws_cloudtrail_rules/aws_network_acl_permissive_entry.py +++ b/rules/aws_cloudtrail_rules/aws_network_acl_permissive_entry.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context from panther_default import aws_cloudtrail_success @@ -9,9 +9,9 @@ def rule(event): # Check if this new NACL entry is allowing traffic from anywhere return ( - deep_get(event, "requestParameters", "cidrBlock") == "0.0.0.0/0" - and deep_get(event, "requestParameters", "ruleAction") == "allow" - and deep_get(event, "requestParameters", "egress") is False + event.deep_get("requestParameters", "cidrBlock") == "0.0.0.0/0" + and event.deep_get("requestParameters", "ruleAction") == "allow" + and event.deep_get("requestParameters", "egress") is False ) diff --git a/rules/aws_cloudtrail_rules/aws_rds_master_pass_updated.py b/rules/aws_cloudtrail_rules/aws_rds_master_pass_updated.py index 6cbac1cb5..1ffe1f345 100644 --- a/rules/aws_cloudtrail_rules/aws_rds_master_pass_updated.py +++ b/rules/aws_cloudtrail_rules/aws_rds_master_pass_updated.py @@ -1,15 +1,10 @@ -from panther_base_helpers import deep_get - - def rule(event): return ( event.get("eventName") == "ModifyDBInstance" and event.get("eventSource") == "rds.amazonaws.com" - and bool(deep_get(event, "responseElements", "pendingModifiedValues", "masterUserPassword")) + and bool(event.deep_get("responseElements", "pendingModifiedValues", "masterUserPassword")) ) def title(event): - return ( - f"RDS Master Password Updated on [{deep_get(event, 'responseElements', 'dBInstanceArn')}]" - ) + return f"RDS Master Password Updated on [{event.deep_get('responseElements', 'dBInstanceArn')}]" diff --git a/rules/aws_cloudtrail_rules/aws_rds_publicrestore.py b/rules/aws_cloudtrail_rules/aws_rds_publicrestore.py index 1079e52ec..3a09a26d3 100644 --- a/rules/aws_cloudtrail_rules/aws_rds_publicrestore.py +++ b/rules/aws_cloudtrail_rules/aws_rds_publicrestore.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context def rule(event): @@ -6,7 +6,7 @@ def rule(event): event.get("eventSource", "") == "rds.amazonaws.com" and event.get("eventName", "") == "RestoreDBInstanceFromDBSnapshot" ): - if deep_get(event, "responseElements", "publiclyAccessible"): + if event.deep_get("responseElements", "publiclyAccessible"): return True return False diff --git a/rules/aws_cloudtrail_rules/aws_rds_snapshot_shared.py b/rules/aws_cloudtrail_rules/aws_rds_snapshot_shared.py index 3729e2e95..b76853904 100644 --- a/rules/aws_cloudtrail_rules/aws_rds_snapshot_shared.py +++ b/rules/aws_cloudtrail_rules/aws_rds_snapshot_shared.py @@ -21,7 +21,7 @@ def rule(event): def title(event): - account_id = event.get("recipientAccountId", default="") + account_id = event.get("recipientAccountId", "") rds_instance_id = event.deep_get( "responseElements", "dBInstanceIdentifier", default="" ) diff --git a/rules/aws_cloudtrail_rules/aws_resource_made_public.py b/rules/aws_cloudtrail_rules/aws_resource_made_public.py index 70614c274..9290d820d 100644 --- a/rules/aws_cloudtrail_rules/aws_resource_made_public.py +++ b/rules/aws_cloudtrail_rules/aws_resource_made_public.py @@ -70,8 +70,7 @@ def rule(event): def title(event): # TODO(): Update this rule to use data models - user = deep_get(event, "userIdentity", "userName") or deep_get( - event, + user = event.deep_get("userIdentity", "userName") or event.deep_get( "userIdentity", "sessionContext", "sessionIssuer", diff --git a/rules/aws_cloudtrail_rules/aws_root_access_key_created.py b/rules/aws_cloudtrail_rules/aws_root_access_key_created.py index 0304333ec..f359799b4 100644 --- a/rules/aws_cloudtrail_rules/aws_root_access_key_created.py +++ b/rules/aws_cloudtrail_rules/aws_root_access_key_created.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context def rule(event): @@ -7,7 +7,7 @@ def rule(event): return False # Only root can create root access keys - if deep_get(event, "userIdentity", "type") != "Root": + if event.deep_get("userIdentity", "type") != "Root": return False # Only alert if the root user is creating an access key for itself diff --git a/rules/aws_cloudtrail_rules/aws_root_activity.py b/rules/aws_cloudtrail_rules/aws_root_activity.py index 1246f21e8..5439f6a90 100644 --- a/rules/aws_cloudtrail_rules/aws_root_activity.py +++ b/rules/aws_cloudtrail_rules/aws_root_activity.py @@ -1,4 +1,3 @@ -from panther_base_helpers import deep_get from panther_default import aws_cloudtrail_success, lookup_aws_account_name EVENT_ALLOW_LIST = {"CreateServiceLinkedRole"} @@ -6,9 +5,9 @@ def rule(event): return ( - deep_get(event, "userIdentity", "type") == "Root" + event.deep_get("userIdentity", "type") == "Root" and aws_cloudtrail_success(event) - and deep_get(event, "userIdentity", "invokedBy") is None + and event.deep_get("userIdentity", "invokedBy") is None and event.get("eventType") != "AwsServiceEvent" and event.get("eventName") not in EVENT_ALLOW_LIST ) @@ -36,10 +35,10 @@ def title(event): def alert_context(event): return { "sourceIPAddress": event.get("sourceIPAddress"), - "userIdentityAccountId": deep_get(event, "userIdentity", "accountId"), - "userIdentityArn": deep_get(event, "userIdentity", "arn"), + "userIdentityAccountId": event.deep_get("userIdentity", "accountId"), + "userIdentityArn": event.deep_get("userIdentity", "arn"), "eventTime": event.get("eventTime"), - "mfaUsed": deep_get(event, "additionalEventData", "MFAUsed"), + "mfaUsed": event.deep_get("additionalEventData", "MFAUsed"), } diff --git a/rules/aws_cloudtrail_rules/aws_root_password_changed.py b/rules/aws_cloudtrail_rules/aws_root_password_changed.py index 59b76cb69..769a0ccc5 100644 --- a/rules/aws_cloudtrail_rules/aws_root_password_changed.py +++ b/rules/aws_cloudtrail_rules/aws_root_password_changed.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context def rule(event): @@ -7,11 +7,11 @@ def rule(event): return False # Only check root activity - if deep_get(event, "userIdentity", "type") != "Root": + if event.deep_get("userIdentity", "type") != "Root": return False # Only alert if the login was a success - return deep_get(event, "responseElements", "PasswordUpdated") == "Success" + return event.deep_get("responseElements", "PasswordUpdated") == "Success" def alert_context(event): diff --git a/rules/aws_cloudtrail_rules/aws_s3_bucket_deleted.py b/rules/aws_cloudtrail_rules/aws_s3_bucket_deleted.py index 73e57f59e..5e8ee9f11 100644 --- a/rules/aws_cloudtrail_rules/aws_s3_bucket_deleted.py +++ b/rules/aws_cloudtrail_rules/aws_s3_bucket_deleted.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context from panther_default import aws_cloudtrail_success @@ -23,7 +23,7 @@ def dedup(event): def title(event): - return f"{deep_get(event, 'userIdentity', 'type')} [{dedup(event)}] destroyed a bucket" + return f"{event.deep_get('userIdentity', 'type')} [{dedup(event)}] destroyed a bucket" def alert_context(event): diff --git a/rules/aws_cloudtrail_rules/aws_s3_bucket_policy_modified.py b/rules/aws_cloudtrail_rules/aws_s3_bucket_policy_modified.py index 5faa33c72..49d92f7d6 100644 --- a/rules/aws_cloudtrail_rules/aws_s3_bucket_policy_modified.py +++ b/rules/aws_cloudtrail_rules/aws_s3_bucket_policy_modified.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context from panther_default import aws_cloudtrail_success # API calls that are indicative of KMS CMK Deletion @@ -20,7 +20,7 @@ def rule(event): def title(event): - return f"S3 bucket modified by [{deep_get(event, 'userIdentity', 'arn')}]" + return f"S3 bucket modified by [{event.deep_get('userIdentity', 'arn')}]" def alert_context(event): diff --git a/rules/aws_cloudtrail_rules/aws_saml_activity.py b/rules/aws_cloudtrail_rules/aws_saml_activity.py index be99ecb62..2a43a1e12 100644 --- a/rules/aws_cloudtrail_rules/aws_saml_activity.py +++ b/rules/aws_cloudtrail_rules/aws_saml_activity.py @@ -1,11 +1,11 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context SAML_ACTIONS = ["UpdateSAMLProvider", "CreateSAMLProvider", "DeleteSAMLProvider"] def rule(event): # Allow AWSSSO to manage - if deep_get(event, "userIdentity", "arn", default="").endswith( + if event.deep_get("userIdentity", "arn", default="").endswith( ":assumed-role/AWSServiceRoleForSSO/AWS-SSO" ): return False @@ -19,7 +19,7 @@ def rule(event): def title(event): return ( - f"[{deep_get(event,'userIdentity','arn')}] " + f"[{event.deep_get('userIdentity','arn')}] " f"performed [{event.get('eventName')}] " f"in account [{event.get('recipientAccountId')}]" ) diff --git a/rules/aws_cloudtrail_rules/aws_security_configuration_change.py b/rules/aws_cloudtrail_rules/aws_security_configuration_change.py index 8bc13018a..1a8e281fd 100644 --- a/rules/aws_cloudtrail_rules/aws_security_configuration_change.py +++ b/rules/aws_cloudtrail_rules/aws_security_configuration_change.py @@ -2,7 +2,7 @@ from fnmatch import fnmatch from unittest.mock import MagicMock -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context from panther_default import aws_cloudtrail_success SECURITY_CONFIG_ACTIONS = { @@ -34,8 +34,7 @@ def rule(event): for entry in ALLOW_LIST: if fnmatch( - deep_get( - event, + event.deep_get( "userIdentity", "sessionContext", "sessionIssuer", @@ -48,14 +47,14 @@ def rule(event): return False if event.get("eventName") == "UpdateDetector": - return not deep_get(event, "requestParameters", "enable", default=True) + return not event.deep_get("requestParameters", "enable", default=True) return event.get("eventName") in SECURITY_CONFIG_ACTIONS def title(event): - user = deep_get(event, "userIdentity", "userName") or deep_get( - event, "userIdentity", "sessionContext", "sessionIssuer", "userName" + user = event.deep_get("userIdentity", "userName") or event.deep_get( + "userIdentity", "sessionContext", "sessionIssuer", "userName" ) return f"Sensitive AWS API call {event.get('eventName')} made by {user}" diff --git a/rules/aws_cloudtrail_rules/aws_software_discovery.py b/rules/aws_cloudtrail_rules/aws_software_discovery.py index 7439361f2..f0b8ca712 100644 --- a/rules/aws_cloudtrail_rules/aws_software_discovery.py +++ b/rules/aws_cloudtrail_rules/aws_software_discovery.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context DISCOVERY_EVENTS = [ "ListDocuments", @@ -29,14 +29,14 @@ def rule(event): def title(event): return ( - f"User [{deep_get(event, 'userIdentity', 'principalId')}] " + f"User [{event.deep_get('userIdentity', 'principalId')}] " f"performed a [{event.get('eventName')}] " f"action in AWS account [{event.get('recipientAccountId')}]." ) def dedup(event): - return deep_get(event, "userIdentity", "principalId", default="NO_PRINCIPAL_ID_FOUND") + return event.deep_get("userIdentity", "principalId", default="NO_PRINCIPAL_ID_FOUND") def alert_context(event): diff --git a/rules/aws_cloudtrail_rules/aws_unauthorized_api_call.py b/rules/aws_cloudtrail_rules/aws_unauthorized_api_call.py index 2f81243e7..e62616c77 100644 --- a/rules/aws_cloudtrail_rules/aws_unauthorized_api_call.py +++ b/rules/aws_cloudtrail_rules/aws_unauthorized_api_call.py @@ -1,6 +1,6 @@ from ipaddress import ip_address -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context # Do not alert on these access denied errors for these events. # Events could be exceptions because they are particularly noisy and provide little to no value, @@ -23,11 +23,11 @@ def rule(event): def dedup(event): - return deep_get(event, "userIdentity", "principalId", default="") + return event.deep_get("userIdentity", "principalId", default="") def title(event): - return f"Access denied to {deep_get(event, 'userIdentity', 'type')} [{dedup(event)}]" + return f"Access denied to {event.deep_get('userIdentity', 'type')} [{dedup(event)}]" def alert_context(event): diff --git a/rules/aws_cloudtrail_rules/aws_unused_region.py b/rules/aws_cloudtrail_rules/aws_unused_region.py index c254793c8..3af9f15da 100644 --- a/rules/aws_cloudtrail_rules/aws_unused_region.py +++ b/rules/aws_cloudtrail_rules/aws_unused_region.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context # Define a list of verboten or unused regions # Could modify to include expected user mappings: { "123456789012": { "us-west-1", "us-east-2" } } @@ -15,7 +15,7 @@ def rule(event): def title(event): - aws_username = deep_get(event, "userIdentity", "sessionContext", "sessionIssuer", "userName") + aws_username = event.deep_get("userIdentity", "sessionContext", "sessionIssuer", "userName") return ( "Non-read-only API call in unused region" f" {event.get('awsRegion', '')} by user {aws_username}" diff --git a/rules/aws_cloudtrail_rules/aws_update_credentials.py b/rules/aws_cloudtrail_rules/aws_update_credentials.py index ffb7de5f4..9e79e2113 100644 --- a/rules/aws_cloudtrail_rules/aws_update_credentials.py +++ b/rules/aws_cloudtrail_rules/aws_update_credentials.py @@ -1,4 +1,4 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context from panther_default import aws_cloudtrail_success UPDATE_EVENTS = {"ChangePassword", "CreateAccessKey", "CreateLoginProfile", "CreateUser"} @@ -9,12 +9,12 @@ def rule(event): def dedup(event): - return deep_get(event, "userIdentity", "userName", default="") + return event.deep_get("userIdentity", "userName", default="") def title(event): return ( - f"{deep_get(event, 'userIdentity', 'type')} [{deep_get(event, 'userIdentity', 'arn')}]" + f"{event.deep_get('userIdentity', 'type')} [{event.deep_get('userIdentity', 'arn')}]" f" has updated their IAM credentials" ) diff --git a/rules/aws_cloudtrail_rules/aws_user_login_profile_modified.py b/rules/aws_cloudtrail_rules/aws_user_login_profile_modified.py index 73999ca06..e706c0597 100644 --- a/rules/aws_cloudtrail_rules/aws_user_login_profile_modified.py +++ b/rules/aws_cloudtrail_rules/aws_user_login_profile_modified.py @@ -1,22 +1,22 @@ -from panther_base_helpers import aws_rule_context, deep_get +from panther_base_helpers import aws_rule_context def rule(event): return ( event.get("eventSource", "") == "iam.amazonaws.com" and event.get("eventName", "") == "UpdateLoginProfile" - and not deep_get(event, "requestParameters", "passwordResetRequired", default=False) - and not deep_get(event, "userIdentity", "arn", default="").endswith( - f"/{deep_get(event, 'requestParameters', 'userName', default='')}" + and not event.deep_get("requestParameters", "passwordResetRequired", default=False) + and not event.deep_get("userIdentity", "arn", default="").endswith( + f"/{event.deep_get('requestParameters', 'userName', default='')}" ) ) def title(event): return ( - f"User [{deep_get(event, 'userIdentity', 'arn').split('/')[-1]}] " + f"User [{event.deep_get('userIdentity', 'arn').split('/')[-1]}] " f"changed the password for " - f"[{deep_get(event, 'requestParameters','userName')}]" + f"[{event.deep_get('requestParameters','userName')}]" ) diff --git a/rules/aws_cloudtrail_rules/aws_waf_disassociation.py b/rules/aws_cloudtrail_rules/aws_waf_disassociation.py index 39ace2f3b..42a325e4f 100644 --- a/rules/aws_cloudtrail_rules/aws_waf_disassociation.py +++ b/rules/aws_cloudtrail_rules/aws_waf_disassociation.py @@ -1,6 +1,3 @@ -from panther_base_helpers import deep_get - - def rule(event): return event.get("eventName") == "DisassociateWebACL" @@ -8,7 +5,7 @@ def rule(event): def title(event): return ( f"AWS Account ID [{event.get('recipientAccountId')}] " - f"disassociated WebACL [{deep_get(event, 'requestParameters', 'resourceArn')}]" + f"disassociated WebACL [{event.deep_get('requestParameters', 'resourceArn')}]" ) @@ -18,6 +15,6 @@ def alert_context(event): "eventName": event.get("eventName"), "recipientAccountId": event.get("recipientAccountId"), "requestID": event.get("requestID"), - "requestParameters": deep_get(event, "requestParameters", "resourceArn"), - "userIdentity": deep_get(event, "userIdentity", "principalId"), + "requestParameters": event.deep_get("requestParameters", "resourceArn"), + "userIdentity": event.deep_get("userIdentity", "principalId"), } diff --git a/rules/aws_eks_rules/system_namespace_public_ip.py b/rules/aws_eks_rules/system_namespace_public_ip.py index 06795689b..2095224b7 100644 --- a/rules/aws_eks_rules/system_namespace_public_ip.py +++ b/rules/aws_eks_rules/system_namespace_public_ip.py @@ -1,6 +1,6 @@ from ipaddress import ip_address -from panther_base_helpers import deep_get, eks_panther_obj_ref +from panther_base_helpers import eks_panther_obj_ref # Explicitly ignore eks:node-manager and eks:addon-manager # which are run as Lambdas and originate from public IPs @@ -22,7 +22,7 @@ def rule(event): if ( p_eks.get("actor") in AMZ_PUBLICS and ":assumed-role/AWSWesleyClusterManagerLambda" - in deep_get(event, "user", "extra", "arn", default=["not found"])[0] + in event.deep_get("user", "extra", "arn", default=["not found"])[0] ): return False if ( diff --git a/rules/aws_guardduty_rules/aws_guardduty_high_sev_findings.py b/rules/aws_guardduty_rules/aws_guardduty_high_sev_findings.py index 2ef274956..97434670f 100644 --- a/rules/aws_guardduty_rules/aws_guardduty_high_sev_findings.py +++ b/rules/aws_guardduty_rules/aws_guardduty_high_sev_findings.py @@ -1,8 +1,8 @@ -from panther_base_helpers import aws_guardduty_context, deep_get +from panther_base_helpers import aws_guardduty_context def rule(event): - if deep_get(event, "service", "additionalInfo", "sample"): + if event.deep_get("service", "additionalInfo", "sample"): # in case of sample data # https://docs.aws.amazon.com/guardduty/latest/ug/sample_findings.html return False diff --git a/rules/aws_guardduty_rules/aws_guardduty_low_sev_findings.py b/rules/aws_guardduty_rules/aws_guardduty_low_sev_findings.py index 2a2071f18..086b05984 100644 --- a/rules/aws_guardduty_rules/aws_guardduty_low_sev_findings.py +++ b/rules/aws_guardduty_rules/aws_guardduty_low_sev_findings.py @@ -1,8 +1,8 @@ -from panther_base_helpers import aws_guardduty_context, deep_get +from panther_base_helpers import aws_guardduty_context def rule(event): - if deep_get(event, "service", "additionalInfo", "sample"): + if event.deep_get("service", "additionalInfo", "sample"): # in case of sample data # https://docs.aws.amazon.com/guardduty/latest/ug/sample_findings.html return False diff --git a/rules/aws_guardduty_rules/aws_guardduty_med_sev_findings.py b/rules/aws_guardduty_rules/aws_guardduty_med_sev_findings.py index 1f5c183de..6931d9567 100644 --- a/rules/aws_guardduty_rules/aws_guardduty_med_sev_findings.py +++ b/rules/aws_guardduty_rules/aws_guardduty_med_sev_findings.py @@ -1,8 +1,8 @@ -from panther_base_helpers import aws_guardduty_context, deep_get +from panther_base_helpers import aws_guardduty_context def rule(event): - if deep_get(event, "service", "additionalInfo", "sample"): + if event.deep_get("service", "additionalInfo", "sample"): # in case of sample data # https://docs.aws.amazon.com/guardduty/latest/ug/sample_findings.html return False diff --git a/rules/azure_signin_rules/azure_failed_signins.py b/rules/azure_signin_rules/azure_failed_signins.py index d8d3917e1..ca7593abd 100644 --- a/rules/azure_signin_rules/azure_failed_signins.py +++ b/rules/azure_signin_rules/azure_failed_signins.py @@ -1,6 +1,5 @@ from global_filter_azuresignin import filter_include_event from panther_azuresignin_helpers import actor_user, azure_signin_alert_context, is_sign_in_event -from panther_base_helpers import deep_get def rule(event): @@ -9,7 +8,7 @@ def rule(event): if not filter_include_event(event): return False - error_code = deep_get(event, "properties", "status", "errorCode", default=0) + error_code = event.deep_get("properties", "status", "errorCode", default=0) return error_code > 0 diff --git a/rules/azure_signin_rules/azure_legacyauth.py b/rules/azure_signin_rules/azure_legacyauth.py index 36fbb6065..a605819e6 100644 --- a/rules/azure_signin_rules/azure_legacyauth.py +++ b/rules/azure_signin_rules/azure_legacyauth.py @@ -3,7 +3,6 @@ from global_filter_azuresignin import filter_include_event from panther_azuresignin_helpers import actor_user, azure_signin_alert_context, is_sign_in_event -from panther_base_helpers import deep_get LEGACY_AUTH_USERAGENTS = ["BAV2ROPC", "CBAInPROD"] # CBAInPROD is reported to be IMAP @@ -23,8 +22,8 @@ def rule(event): return False if actor_user(event) in KNOWN_EXCEPTIONS: return False - user_agent = deep_get(event, "properties", "userAgent", default="") - error_code = deep_get(event, "properties", "status", "errorCode", default=0) + user_agent = event.deep_get("properties", "userAgent", default="") + error_code = event.deep_get("properties", "status", "errorCode", default=0) return all([user_agent in LEGACY_AUTH_USERAGENTS, error_code == 0]) @@ -45,5 +44,5 @@ def dedup(event): def alert_context(event): a_c = azure_signin_alert_context(event) - a_c["userAgent"] = deep_get(event, "properties", "userAgent", "") + a_c["userAgent"] = event.deep_get("properties", "userAgent", "") return a_c diff --git a/rules/azure_signin_rules/azure_risklevel_passthrough.py b/rules/azure_signin_rules/azure_risklevel_passthrough.py index 3972c1d0b..515bca767 100644 --- a/rules/azure_signin_rules/azure_risklevel_passthrough.py +++ b/rules/azure_signin_rules/azure_risklevel_passthrough.py @@ -1,6 +1,5 @@ from global_filter_azuresignin import filter_include_event from panther_azuresignin_helpers import actor_user, azure_signin_alert_context, is_sign_in_event -from panther_base_helpers import deep_get PASSTHROUGH_SEVERITIES = {"low", "medium", "high"} @@ -14,15 +13,15 @@ def rule(event): global IDENTIFIED_RISK_LEVEL # pylint: disable=global-variable-undefined IDENTIFIED_RISK_LEVEL = "" # Do not pass through risks marked as dismissed or remediated in AD - if deep_get(event, "properties", "riskState", default="").lower() in [ + if event.deep_get("properties", "riskState", default="").lower() in [ "dismissed", "remediated", ]: return False # check riskLevelAggregated for risk_type in ["riskLevelAggregated", "riskLevelDuringSignIn"]: - if deep_get(event, "properties", risk_type, default="").lower() in PASSTHROUGH_SEVERITIES: - IDENTIFIED_RISK_LEVEL = deep_get(event, "properties", risk_type).lower() + if event.deep_get("properties", risk_type, default="").lower() in PASSTHROUGH_SEVERITIES: + IDENTIFIED_RISK_LEVEL = event.deep_get("properties", risk_type).lower() return True return False diff --git a/rules/box_rules/box_access_granted.py b/rules/box_rules/box_access_granted.py index 1d09f8b11..10a34c1c3 100644 --- a/rules/box_rules/box_access_granted.py +++ b/rules/box_rules/box_access_granted.py @@ -1,12 +1,9 @@ -from panther_base_helpers import deep_get - - def rule(event): return event.get("event_type") == "ACCESS_GRANTED" def title(event): return ( - f"User [{deep_get(event, 'created_by', 'name', default='')}] granted " + f"User [{event.deep_get('created_by', 'name', default='')}] granted " f"access to their account" ) diff --git a/rules/box_rules/box_anomalous_download.py b/rules/box_rules/box_anomalous_download.py index fa6550efc..6e96785f0 100644 --- a/rules/box_rules/box_anomalous_download.py +++ b/rules/box_rules/box_anomalous_download.py @@ -19,5 +19,5 @@ def title(event): return description return ( f"Anomalous download activity triggered by user " - f"[{deep_get(event, 'created_by', 'name', default='')}]." + f"[{event.deep_get('created_by', 'name', default='')}]." ) diff --git a/rules/box_rules/box_event_triggered_externally.py b/rules/box_rules/box_event_triggered_externally.py index defda7226..a882a39da 100644 --- a/rules/box_rules/box_event_triggered_externally.py +++ b/rules/box_rules/box_event_triggered_externally.py @@ -1,4 +1,3 @@ -from panther_base_helpers import deep_get from panther_config import config DOMAINS = {"@" + domain for domain in config.ORGANIZATION_DOMAINS} @@ -19,6 +18,6 @@ def rule(event): def title(event): return ( - f"External user [{deep_get(event, 'created_by', 'login', default='')}] " + f"External user [{event.deep_get('created_by', 'login', default='')}] " f"triggered a box event." ) diff --git a/rules/box_rules/box_item_shared_externally.py b/rules/box_rules/box_item_shared_externally.py index 49a3f19be..aaa0898c5 100644 --- a/rules/box_rules/box_item_shared_externally.py +++ b/rules/box_rules/box_item_shared_externally.py @@ -24,18 +24,18 @@ def rule(event): def get_item(event): - item_id = deep_get(event, "source", "item_id", default="") - user_id = deep_get(event, "source", "owned_by", "id", default="") + item_id = event.deep_get("source", "item_id", default="") + user_id = event.deep_get("source", "owned_by", "id", default="") item = {} - if deep_get(event, "source", "item_type") == "folder": + if event.deep_get("source", "item_type") == "folder": item = lookup_box_folder(user_id, item_id) - elif deep_get(event, "source", "item_type") == "file": + elif event.deep_get("source", "item_type") == "file": item = lookup_box_file(user_id, item_id) return item def title(event): return ( - f"User [{deep_get(event, 'created_by', 'login', default='')}] shared an item " - f"[{deep_get(event, 'source', 'item_name', default='')}] externally." + f"User [{event.deep_get('created_by', 'login', default='')}] shared an item " + f"[{event.deep_get('source', 'item_name', default='')}] externally." ) diff --git a/rules/box_rules/box_malicious_content.py b/rules/box_rules/box_malicious_content.py index f6b294ab0..ab040fa5b 100644 --- a/rules/box_rules/box_malicious_content.py +++ b/rules/box_rules/box_malicious_content.py @@ -18,8 +18,8 @@ def rule(event): def title(event): if event.get("event_type") == "FILE_MARKED_MALICIOUS": return ( - f"File [{deep_get(event, 'source', 'item_name', default='')}], owned by " - f"[{deep_get(event, 'source', 'owned_by', 'login', default='')}], " + f"File [{event.deep_get('source', 'item_name', default='')}], owned by " + f"[{event.deep_get('source', 'owned_by', 'login', default='')}], " f"was marked malicious." ) diff --git a/rules/box_rules/box_new_login.py b/rules/box_rules/box_new_login.py index 80e6d60e2..1a064e38f 100644 --- a/rules/box_rules/box_new_login.py +++ b/rules/box_rules/box_new_login.py @@ -1,6 +1,3 @@ -from panther_base_helpers import deep_get - - def rule(event): # ADD_LOGIN_ACTIVITY_DEVICE # detect when a user logs in from a device not previously seen @@ -9,6 +6,6 @@ def rule(event): def title(event): return ( - f"User [{deep_get(event, 'created_by', 'name', default='')}] " + f"User [{event.deep_get('created_by', 'name', default='')}] " f"logged in from a new device." ) diff --git a/rules/box_rules/box_policy_violation.py b/rules/box_rules/box_policy_violation.py index 05a055eee..5aba51d38 100644 --- a/rules/box_rules/box_policy_violation.py +++ b/rules/box_rules/box_policy_violation.py @@ -1,5 +1,3 @@ -from panther_base_helpers import deep_get - POLICY_VIOLATIONS = { "CONTENT_WORKFLOW_UPLOAD_POLICY_VIOLATION", "CONTENT_WORKFLOW_SHARING_POLICY_VIOLATION", @@ -12,6 +10,6 @@ def rule(event): def title(event): return ( - f"User [{deep_get(event, 'created_by', 'name', default='')}] " + f"User [{event.deep_get('created_by', 'name', default='')}] " f"violated a content workflow policy." ) diff --git a/rules/box_rules/box_untrusted_device.py b/rules/box_rules/box_untrusted_device.py index 58a4eb70d..2511c3261 100644 --- a/rules/box_rules/box_untrusted_device.py +++ b/rules/box_rules/box_untrusted_device.py @@ -1,6 +1,3 @@ -from panther_base_helpers import deep_get - - def rule(event): # DEVICE_TRUST_CHECK_FAILED # detect when a user attempts to login from an untrusted device @@ -9,6 +6,6 @@ def rule(event): def title(event): return ( - f"User [{deep_get(event, 'created_by', 'name', default='')}] " + f"User [{event.deep_get('created_by', 'name', default='')}] " f"attempted to login from an untrusted device." ) diff --git a/rules/box_rules/box_user_downloads.py b/rules/box_rules/box_user_downloads.py index 535379224..90a70c082 100644 --- a/rules/box_rules/box_user_downloads.py +++ b/rules/box_rules/box_user_downloads.py @@ -1,12 +1,9 @@ -from panther_base_helpers import deep_get - - def rule(event): return event.get("event_type") == "DOWNLOAD" def title(event): return ( - f"User [{deep_get(event, 'created_by', 'login', default='')}] " + f"User [{event.deep_get('created_by', 'login', default='')}] " f"exceeded threshold for number of downloads in the configured time frame." ) diff --git a/rules/box_rules/box_user_permission_updates.py b/rules/box_rules/box_user_permission_updates.py index cf51e0432..1026b2fd1 100644 --- a/rules/box_rules/box_user_permission_updates.py +++ b/rules/box_rules/box_user_permission_updates.py @@ -1,5 +1,3 @@ -from panther_base_helpers import deep_get - PERMISSION_UPDATE_EVENT_TYPES = { "CHANGE_FOLDER_PERMISSION", "ITEM_SHARED_CREATE", @@ -14,6 +12,6 @@ def rule(event): def title(event): return ( - f"User [{deep_get(event, 'created_by', 'login', default='')}]" + f"User [{event.deep_get('created_by', 'login', default='')}]" f" exceeded threshold for number of permission changes in the configured time frame." ) diff --git a/rules/crowdstrike_rules/crowdstrike_macos_add_trusted_cert.py b/rules/crowdstrike_rules/crowdstrike_macos_add_trusted_cert.py index a7f1559da..8dfda439a 100644 --- a/rules/crowdstrike_rules/crowdstrike_macos_add_trusted_cert.py +++ b/rules/crowdstrike_rules/crowdstrike_macos_add_trusted_cert.py @@ -2,8 +2,8 @@ def rule(event): - event_platform = event.deep_get("event_platform", default="") - fdr_event_type = event.deep_get("fdr_event_type", default="") + event_platform = event.get("event_platform", "") + fdr_event_type = event.get("fdr_event_type", "") image_filename = event.deep_get("event", "ImageFileName", default="") command_line = event.deep_get("event", "CommandLine", default="") return all( diff --git a/rules/crowdstrike_rules/crowdstrike_macos_osascript_administrator.py b/rules/crowdstrike_rules/crowdstrike_macos_osascript_administrator.py index 9a33eac98..c8a2b03a9 100644 --- a/rules/crowdstrike_rules/crowdstrike_macos_osascript_administrator.py +++ b/rules/crowdstrike_rules/crowdstrike_macos_osascript_administrator.py @@ -2,8 +2,8 @@ def rule(event): - event_platform = event.deep_get("event_platform", default="") - event_simplename = event.deep_get("event_simplename", default="") + event_platform = event.get("event_platform", "") + event_simplename = event.get("event_simplename", "") image_filename = event.deep_get("event", "ImageFileName", default="") command_line = event.deep_get("event", "CommandLine", default="") return all( diff --git a/rules/crowdstrike_rules/crowdstrike_macos_plutil_usage.py b/rules/crowdstrike_rules/crowdstrike_macos_plutil_usage.py index ba272f888..e49f2e312 100644 --- a/rules/crowdstrike_rules/crowdstrike_macos_plutil_usage.py +++ b/rules/crowdstrike_rules/crowdstrike_macos_plutil_usage.py @@ -9,8 +9,8 @@ def rule(event): ): return False - event_platform = event.deep_get("event_platform", default="") - fdr_event_type = event.deep_get("fdr_event_type", default="") + event_platform = event.get("event_platform", "") + fdr_event_type = event.get("fdr_event_type", "") image_filename = event.deep_get("event", "ImageFileName", default="") return all( diff --git a/rules/dropbox_rules/dropbox_admin_sign_in_as_session.py b/rules/dropbox_rules/dropbox_admin_sign_in_as_session.py index fff38699e..1d841910c 100644 --- a/rules/dropbox_rules/dropbox_admin_sign_in_as_session.py +++ b/rules/dropbox_rules/dropbox_admin_sign_in_as_session.py @@ -1,11 +1,8 @@ -from panther_base_helpers import deep_get - - def rule(event): - return deep_get(event, "event_type", "_tag", default="") == "sign_in_as_session_start" + return event.deep_get("event_type", "_tag", default="") == "sign_in_as_session_start" def title(event): - actor = deep_get(event, "actor", "admin", "email", default="") - target = deep_get(event, "context", "email", default="") + actor = event.deep_get("actor", "admin", "email", default="") + target = event.deep_get("context", "email", default="") return f"Dropbox: Admin [{actor}] started a sign-in-as session as user [{target}]." diff --git a/rules/dropbox_rules/dropbox_external_share.py b/rules/dropbox_rules/dropbox_external_share.py index 86a9f206a..813749567 100644 --- a/rules/dropbox_rules/dropbox_external_share.py +++ b/rules/dropbox_rules/dropbox_external_share.py @@ -1,7 +1,6 @@ import json from unittest.mock import MagicMock -from panther_base_helpers import deep_get from panther_config import config DROPBOX_ALLOWED_SHARE_DOMAINS = config.DROPBOX_ALLOWED_SHARE_DOMAINS @@ -13,7 +12,7 @@ def rule(event): DROPBOX_ALLOWED_SHARE_DOMAINS = set( json.loads(DROPBOX_ALLOWED_SHARE_DOMAINS()) ) # pylint: disable=not-callable - if deep_get(event, "event_type", "_tag", default="") == "shared_content_add_member": + if event.deep_get("event_type", "_tag", default="") == "shared_content_add_member": participants = event.get("participants", [{}]) for participant in participants: email = participant.get("user", {}).get("email", "") @@ -23,7 +22,7 @@ def rule(event): def title(event): - actor = deep_get(event, "actor", "user", "email", default="") + actor = event.deep_get("actor", "user", "email", default="") assets = [e.get("display_name", "") for e in event.get("assets", [{}])] participants = event.get("participants", [{}]) external_participants = [] diff --git a/rules/dropbox_rules/dropbox_linked_team_application_added.py b/rules/dropbox_rules/dropbox_linked_team_application_added.py index 74dce17e4..ec3a47691 100644 --- a/rules/dropbox_rules/dropbox_linked_team_application_added.py +++ b/rules/dropbox_rules/dropbox_linked_team_application_added.py @@ -1,11 +1,8 @@ -from panther_base_helpers import deep_get - - def rule(event): return all( [ - deep_get(event, "event_type", "_tag", default="") == "app_link_team", - deep_get(event, "event_type", "description", default="") == "Linked app for team", + event.deep_get("event_type", "_tag", default="") == "app_link_team", + event.deep_get("event_type", "description", default="") == "Linked app for team", ] ) @@ -39,8 +36,8 @@ def title(event): # find the intersection and use that for the key actor_key = set(tuple(event.get("actor", {}).keys())).intersection(get_actor_type()) if len(actor_key) == 1: - display_name = deep_get( - event, "actor", tuple(actor_key)[0], "display_name", default="" + display_name = event.deep_get( + "actor", tuple(actor_key)[0], "display_name", default="" ) # Explicitly use "" if we find any length of keys != 1 else: @@ -64,13 +61,13 @@ def alert_context(event): additional_user_details = user_details(event) return { "additional_user_details": additional_user_details, - "app_display_name": deep_get( - event, "details", "app_info", "display_name", default="" + "app_display_name": event.deep_get( + "details", "app_info", "display_name", default="" ), - "ip_address": deep_get( - event, "origin", "geo_location", "ip_address", default="" + "ip_address": event.deep_get( + "origin", "geo_location", "ip_address", default="" ), - "request_id": deep_get( - event, "origin", "access_method", "request_id", default="" + "request_id": event.deep_get( + "origin", "access_method", "request_id", default="" ), } diff --git a/rules/dropbox_rules/dropbox_ownership_transfer.py b/rules/dropbox_rules/dropbox_ownership_transfer.py index 0392372c6..a12597a94 100644 --- a/rules/dropbox_rules/dropbox_ownership_transfer.py +++ b/rules/dropbox_rules/dropbox_ownership_transfer.py @@ -1,22 +1,21 @@ import json from unittest.mock import MagicMock -from panther_base_helpers import deep_get from panther_config import config DROPBOX_TRUSTED_OWNERSHIP_DOMAINS = config.DROPBOX_TRUSTED_OWNERSHIP_DOMAINS def rule(event): - return "Transferred ownership " in deep_get(event, "event_type", "description", default="") + return "Transferred ownership " in event.deep_get("event_type", "description", default="") def title(event): - actor = deep_get(event, "actor", "user", "email", default="") - previous_owner = deep_get( - event, "details", "previous_owner_email", default="" + actor = event.deep_get("actor", "user", "email", default="") + previous_owner = event.deep_get( + "details", "previous_owner_email", default="" ) - new_owner = deep_get(event, "details", "new_owner_email", default="") + new_owner = event.deep_get("details", "new_owner_email", default="") assets = event.get("assets", [{}]) asset = [a.get("display_name", "") for a in assets] return ( @@ -31,7 +30,7 @@ def severity(event): DROPBOX_TRUSTED_OWNERSHIP_DOMAINS = set( json.loads(DROPBOX_TRUSTED_OWNERSHIP_DOMAINS()) ) # pylint: disable=not-callable - new_owner = deep_get(event, "details", "new_owner_email", default="") + new_owner = event.deep_get("details", "new_owner_email", default="") if new_owner.split("@")[-1] not in DROPBOX_TRUSTED_OWNERSHIP_DOMAINS: return "HIGH" return "LOW" diff --git a/rules/dropbox_rules/dropbox_user_disabled_2fa.py b/rules/dropbox_rules/dropbox_user_disabled_2fa.py index 5667d003f..0c9cfa954 100644 --- a/rules/dropbox_rules/dropbox_user_disabled_2fa.py +++ b/rules/dropbox_rules/dropbox_user_disabled_2fa.py @@ -1,16 +1,13 @@ -from panther_base_helpers import deep_get - - def rule(event): return all( [ - deep_get(event, "details", ".tag", default="") == "tfa_change_status_details", - deep_get(event, "details", "new_value", ".tag") == "disabled", + event.deep_get("details", ".tag", default="") == "tfa_change_status_details", + event.deep_get("details", "new_value", ".tag") == "disabled", ] ) def title(event): - actor = deep_get(event, "actor", "user", "email", default="") - target = deep_get(event, "context", "email", default="") + actor = event.deep_get("actor", "user", "email", default="") + target = event.deep_get("context", "email", default="") return f"Dropbox: [{actor}] disabled 2FA for [{target}]." diff --git a/rules/duo_rules/duo_user_action_fraudulent.py b/rules/duo_rules/duo_user_action_fraudulent.py index ac67946c9..102e1aac4 100644 --- a/rules/duo_rules/duo_user_action_fraudulent.py +++ b/rules/duo_rules/duo_user_action_fraudulent.py @@ -1,12 +1,9 @@ -from panther_base_helpers import deep_get - - def rule(event): return event.get("result") == "fraud" def title(event): - user = deep_get(event, "user", "name", default="Unknown") + user = event.deep_get("user", "name", default="Unknown") return f"A Duo action was marked as fraudulent by [{user}]" @@ -14,9 +11,9 @@ def alert_context(event): return { "factor": event.get("factor"), "reason": event.get("reason"), - "user": deep_get(event, "user", "name", default=""), - "os": deep_get(event, "access_device", "os", default=""), - "ip_access": deep_get(event, "access_device", "ip", default=""), - "ip_auth": deep_get(event, "auth_device", "ip", default=""), - "application": deep_get(event, "application", "name", default=""), + "user": event.deep_get("user", "name", default=""), + "os": event.deep_get("access_device", "os", default=""), + "ip_access": event.deep_get("access_device", "ip", default=""), + "ip_auth": event.deep_get("auth_device", "ip", default=""), + "application": event.deep_get("application", "name", default=""), } diff --git a/rules/duo_rules/duo_user_anomalous_push.py b/rules/duo_rules/duo_user_anomalous_push.py index 9e4a7a766..8400bbc59 100644 --- a/rules/duo_rules/duo_user_anomalous_push.py +++ b/rules/duo_rules/duo_user_anomalous_push.py @@ -1,12 +1,9 @@ -from panther_base_helpers import deep_get - - def rule(event): return event.get("reason") == "anomalous_push" and event.get("result") == "denied" def title(event): - user = deep_get(event, "user", "name", default="Unknown") + user = event.deep_get("user", "name", default="Unknown") return f"Duo Auth denied due to an anomalous 2FA push for [{user}]" @@ -14,9 +11,9 @@ def alert_context(event): return { "factor": event.get("factor"), "reason": event.get("reason"), - "user": deep_get(event, "user", "name", default=""), - "os": deep_get(event, "access_device", "os", default=""), - "ip_access": deep_get(event, "access_device", "ip", default=""), - "ip_auth": deep_get(event, "auth_device", "ip", default=""), - "application": deep_get(event, "application", "name", default=""), + "user": event.deep_get("user", "name", default=""), + "os": event.deep_get("access_device", "os", default=""), + "ip_access": event.deep_get("access_device", "ip", default=""), + "ip_auth": event.deep_get("auth_device", "ip", default=""), + "application": event.deep_get("application", "name", default=""), } diff --git a/rules/duo_rules/duo_user_bypass_code_used.py b/rules/duo_rules/duo_user_bypass_code_used.py index a8ac568c8..dbfbf9366 100644 --- a/rules/duo_rules/duo_user_bypass_code_used.py +++ b/rules/duo_rules/duo_user_bypass_code_used.py @@ -1,12 +1,9 @@ -from panther_base_helpers import deep_get - - def rule(event): return event.get("reason") == "bypass_user" and event.get("result") == "success" def title(event): - user = deep_get(event, "user", "name", default="Unknown") + user = event.deep_get("user", "name", default="Unknown") return f"Bypass code for Duo User [{user}] used" @@ -14,9 +11,9 @@ def alert_context(event): return { "factor": event.get("factor"), "reason": event.get("reason"), - "user": deep_get(event, "user", "name", default=""), - "os": deep_get(event, "access_device", "os", default=""), - "ip_access": deep_get(event, "access_device", "ip", default=""), - "ip_auth": deep_get(event, "auth_device", "ip", default=""), - "application": deep_get(event, "application", "name", default=""), + "user": event.deep_get("user", "name", default=""), + "os": event.deep_get("access_device", "os", default=""), + "ip_access": event.deep_get("access_device", "ip", default=""), + "ip_auth": event.deep_get("auth_device", "ip", default=""), + "application": event.deep_get("application", "name", default=""), } diff --git a/rules/duo_rules/duo_user_endpoint_failure_multi.py b/rules/duo_rules/duo_user_endpoint_failure_multi.py index c6bec282f..5053905b7 100644 --- a/rules/duo_rules/duo_user_endpoint_failure_multi.py +++ b/rules/duo_rules/duo_user_endpoint_failure_multi.py @@ -1,6 +1,3 @@ -from panther_base_helpers import deep_get - - def rule(event): endpoint_reasons = [ "endpoint_is_not_in_management_system", @@ -13,7 +10,7 @@ def rule(event): def title(event): - user = deep_get(event, "user", "name", default="Unknown") + user = event.deep_get("user", "name", default="Unknown") reason = event.get("reason", "Unknown") return f"Duo User [{user}] encountered suspicious endpoint issue [{reason}]" @@ -22,9 +19,9 @@ def alert_context(event): return { "factor": event.get("factor"), "reason": event.get("reason"), - "user": deep_get(event, "user", "name", default=""), - "os": deep_get(event, "access_device", "os", default=""), - "ip_access": deep_get(event, "access_device", "ip", default=""), - "ip_auth": deep_get(event, "auth_device", "ip", default=""), - "application": deep_get(event, "application", "name", default=""), + "user": event.deep_get("user", "name", default=""), + "os": event.deep_get("access_device", "os", default=""), + "ip_access": event.deep_get("access_device", "ip", default=""), + "ip_auth": event.deep_get("auth_device", "ip", default=""), + "application": event.deep_get("application", "name", default=""), } diff --git a/rules/gcp_audit_rules/gcp_access_attempts_violating_vpc_service_controls.py b/rules/gcp_audit_rules/gcp_access_attempts_violating_vpc_service_controls.py index 9eb8fcb7a..87027352c 100644 --- a/rules/gcp_audit_rules/gcp_access_attempts_violating_vpc_service_controls.py +++ b/rules/gcp_audit_rules/gcp_access_attempts_violating_vpc_service_controls.py @@ -1,11 +1,8 @@ -from panther_base_helpers import deep_get, deep_walk - - def rule(event): - severity = deep_get(event, "severity", default="") - status_code = deep_get(event, "protoPayload", "status", "code", default="") - violation_types = deep_walk( - event, "protoPayload", "status", "details", "violations", "type", default=[] + severity = event.get("severity", "") + status_code = event.deep_get("protoPayload", "status", "code", default="") + violation_types = event.deep_walk( + "protoPayload", "status", "details", "violations", "type", default=[] ) if all( [ @@ -19,8 +16,8 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - method = deep_get(event, "protoPayload", "methodName", default="") + method = event.deep_get("protoPayload", "methodName", default="") return f"GCP: [{actor}] performed a [{method}] request that violates VPC Service Controls" diff --git a/rules/gcp_audit_rules/gcp_bigquery_large_scan.py b/rules/gcp_audit_rules/gcp_bigquery_large_scan.py index ea3e0bbd7..4c22ba70d 100644 --- a/rules/gcp_audit_rules/gcp_bigquery_large_scan.py +++ b/rules/gcp_audit_rules/gcp_bigquery_large_scan.py @@ -1,5 +1,3 @@ -from panther_base_helpers import deep_get - # 1.07 GB QUERY_THRESHOLD_BYTES = 1073741824 @@ -7,12 +5,11 @@ def rule(event): return all( [ - deep_get(event, "resource", "type", default="").startswith("bigquery"), - deep_get(event, "operation", "last") is True, - deep_get(event, "protoPayload", "metadata", "jobChange", "job", "jobConfig", "type") + event.deep_get("resource", "type", default="").startswith("bigquery"), + event.deep_get("operation", "last") is True, + event.deep_get("protoPayload", "metadata", "jobChange", "job", "jobConfig", "type") == "QUERY", - deep_get( - event, + event.deep_get( "protoPayload", "metadata", "jobChange", @@ -23,8 +20,7 @@ def rule(event): ) == "SELECT", int( - deep_get( - event, + event.deep_get( "protoPayload", "metadata", "jobChange", @@ -41,11 +37,10 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - query_size = deep_get( - event, + query_size = event.deep_get( "protoPayload", "metadata", "jobChange", @@ -60,8 +55,7 @@ def title(event): def alert_context(event): return { - "query": deep_get( - event, + "query": event.deep_get( "protoPayload", "metadata", "jobChange", @@ -71,15 +65,13 @@ def alert_context(event): "query", default="", ), - "actor": deep_get( - event, + "actor": event.deep_get( "protoPayload", "authenticationInfo", "principalEmail", default="", ), - "query_size": deep_get( - event, + "query_size": event.deep_get( "protoPayload", "metadata", "jobChange", diff --git a/rules/gcp_audit_rules/gcp_cloud_run_service_created.py b/rules/gcp_audit_rules/gcp_cloud_run_service_created.py index 488f29588..2a29a4ff8 100644 --- a/rules/gcp_audit_rules/gcp_cloud_run_service_created.py +++ b/rules/gcp_audit_rules/gcp_cloud_run_service_created.py @@ -1,16 +1,15 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get, deep_walk def rule(event): - if deep_get(event, "severity") == "ERROR": + if event.get("severity") == "ERROR": return False - method_name = deep_get(event, "protoPayload", "methodName", default="") + method_name = event.deep_get("protoPayload", "methodName", default="") if not method_name.endswith("Services.CreateService"): return False - authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + authorization_info = event.deep_walk("protoPayload", "authorizationInfo") if not authorization_info: return False @@ -21,18 +20,17 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - project_id = deep_get(event, "resource", "labels", "project_id", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return f"[GCP]: [{actor}] created new Run Service in project [{project_id}]" def alert_context(event): context = gcp_alert_context(event) - context["service_account"] = deep_get( - event, + context["service_account"] = event.deep_get( "protoPayload", "request", "service", diff --git a/rules/gcp_audit_rules/gcp_cloud_run_set_iam_policy.py b/rules/gcp_audit_rules/gcp_cloud_run_set_iam_policy.py index 09e33a44a..48b21472f 100644 --- a/rules/gcp_audit_rules/gcp_cloud_run_set_iam_policy.py +++ b/rules/gcp_audit_rules/gcp_cloud_run_set_iam_policy.py @@ -1,16 +1,15 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get, deep_walk def rule(event): - if deep_get(event, "severity") == "ERROR": + if event.get("severity") == "ERROR": return False - method_name = deep_get(event, "protoPayload", "methodName", default="") + method_name = event.deep_get("protoPayload", "methodName", default="") if not method_name.endswith("Services.SetIamPolicy"): return False - authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + authorization_info = event.deep_walk("protoPayload", "authorizationInfo") if not authorization_info: return False @@ -21,12 +20,12 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - resource = deep_get(event, "resource", "resourceName", default="") - assigned_role = deep_walk(event, "protoPayload", "response", "bindings", "role") - project_id = deep_get(event, "resource", "labels", "project_id", default="") + resource = event.deep_get("resource", "resourceName", default="") + assigned_role = event.deep_walk("protoPayload", "response", "bindings", "role") + project_id = event.deep_get("resource", "labels", "project_id", default="") return ( f"[GCP]: [{actor}] was granted access to [{resource}] service with " @@ -36,8 +35,7 @@ def title(event): def alert_context(event): context = gcp_alert_context(event) - context["assigned_role"] = deep_walk( - event, + context["assigned_role"] = event.deep_walk( "protoPayload", "response", "bindings", diff --git a/rules/gcp_audit_rules/gcp_cloud_storage_buckets_modified_or_deleted.py b/rules/gcp_audit_rules/gcp_cloud_storage_buckets_modified_or_deleted.py index be8fd958d..139a4e59c 100644 --- a/rules/gcp_audit_rules/gcp_cloud_storage_buckets_modified_or_deleted.py +++ b/rules/gcp_audit_rules/gcp_cloud_storage_buckets_modified_or_deleted.py @@ -1,23 +1,21 @@ -from panther_base_helpers import deep_get - BUCKET_OPERATIONS = ["storage.buckets.delete", "storage.buckets.update"] def rule(event): return all( [ - deep_get(event, "protoPayload", "serviceName", default="") == "storage.googleapis.com", - deep_get(event, "protoPayload", "methodName", default="") in BUCKET_OPERATIONS, + event.deep_get("protoPayload", "serviceName", default="") == "storage.googleapis.com", + event.deep_get("protoPayload", "methodName", default="") in BUCKET_OPERATIONS, ] ) def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - operation = deep_get(event, "protoPayload", "methodName", default="") - project = deep_get(event, "resource", "labels", "project_id", default="") - bucket = deep_get(event, "resource", "labels", "bucket_name", default="") + operation = event.deep_get("protoPayload", "methodName", default="") + project = event.deep_get("resource", "labels", "project_id", default="") + bucket = event.deep_get("resource", "labels", "bucket_name", default="") return f"GCP: [{actor}] performed a [{operation}] on bucket [{bucket}] in project [{project}]." diff --git a/rules/gcp_audit_rules/gcp_cloudbuild_potential_privilege_escalation.py b/rules/gcp_audit_rules/gcp_cloudbuild_potential_privilege_escalation.py index c3d7eb243..d6dd3a946 100644 --- a/rules/gcp_audit_rules/gcp_cloudbuild_potential_privilege_escalation.py +++ b/rules/gcp_audit_rules/gcp_cloudbuild_potential_privilege_escalation.py @@ -1,14 +1,13 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get, deep_walk def rule(event): - if not deep_get(event, "protoPayload", "methodName", default="METHOD_NOT_FOUND").endswith( + if not event.deep_get("protoPayload", "methodName", default="METHOD_NOT_FOUND").endswith( "CloudBuild.CreateBuild" ): return False - authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + authorization_info = event.deep_walk("protoPayload", "authorizationInfo") if not authorization_info: return False @@ -19,11 +18,11 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - operation = deep_get(event, "protoPayload", "methodName", default="") - project_id = deep_get(event, "resource", "labels", "project_id", default="") + operation = event.deep_get("protoPayload", "methodName", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return f"[GCP]: [{actor}] performed [{operation}] on project [{project_id}]" diff --git a/rules/gcp_audit_rules/gcp_cloudfunctions_functions_create.py b/rules/gcp_audit_rules/gcp_cloudfunctions_functions_create.py index ad90a1ff6..d46bbbef7 100644 --- a/rules/gcp_audit_rules/gcp_cloudfunctions_functions_create.py +++ b/rules/gcp_audit_rules/gcp_cloudfunctions_functions_create.py @@ -1,9 +1,8 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get, deep_walk def rule(event): - authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + authorization_info = event.deep_walk("protoPayload", "authorizationInfo") if not authorization_info: return False @@ -17,11 +16,11 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - operation = deep_get(event, "protoPayload", "methodName", default="") - project_id = deep_get(event, "resource", "labels", "project_id", default="") + operation = event.deep_get("protoPayload", "methodName", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return f"[GCP]: [{actor}] performed [{operation}] on project [{project_id}]" diff --git a/rules/gcp_audit_rules/gcp_cloudfunctions_functions_update.py b/rules/gcp_audit_rules/gcp_cloudfunctions_functions_update.py index a54a89074..c72a2535f 100644 --- a/rules/gcp_audit_rules/gcp_cloudfunctions_functions_update.py +++ b/rules/gcp_audit_rules/gcp_cloudfunctions_functions_update.py @@ -1,9 +1,8 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get, deep_walk def rule(event): - authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + authorization_info = event.deep_walk("protoPayload", "authorizationInfo") if not authorization_info: return False @@ -17,11 +16,11 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - operation = deep_get(event, "protoPayload", "methodName", default="") - project_id = deep_get(event, "resource", "labels", "project_id", default="") + operation = event.deep_get("protoPayload", "methodName", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return f"[GCP]: [{actor}] performed [{operation}] on project [{project_id}]" diff --git a/rules/gcp_audit_rules/gcp_computeinstances_create_privilege_escalation.py b/rules/gcp_audit_rules/gcp_computeinstances_create_privilege_escalation.py index 386009956..f37ae5de5 100644 --- a/rules/gcp_audit_rules/gcp_computeinstances_create_privilege_escalation.py +++ b/rules/gcp_audit_rules/gcp_computeinstances_create_privilege_escalation.py @@ -1,5 +1,4 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get REQUIRED_PERMISSIONS = [ "compute.disks.create", @@ -12,14 +11,14 @@ def rule(event): - if deep_get(event, "protoPayload", "response", "error"): + if event.deep_get("protoPayload", "response", "error"): return False - method = deep_get(event, "protoPayload", "methodName", default="METHOD_NOT_FOUND") + method = event.deep_get("protoPayload", "methodName", default="METHOD_NOT_FOUND") if not method.endswith("compute.instances.insert"): return False - authorization_info = deep_get(event, "protoPayload", "authorizationInfo") + authorization_info = event.deep_get("protoPayload", "authorizationInfo") if not authorization_info: return False @@ -34,17 +33,17 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - service_accounts = deep_get(event, "protoPayload", "request", "serviceAccounts") + service_accounts = event.deep_get("protoPayload", "request", "serviceAccounts") if not service_accounts: service_account_emails = "" else: service_account_emails = [service_acc["email"] for service_acc in service_accounts] - project = deep_get(event, "resource", "labels", "project_id", default="") + project = event.deep_get("resource", "labels", "project_id", default="") return ( f"[GCP]: [{actor}] created a new Compute Engine instance with [{service_account_emails}] " f"Service Account on project [{project}]" @@ -53,7 +52,7 @@ def title(event): def alert_context(event): context = gcp_alert_context(event) - service_accounts = deep_get(event, "protoPayload", "request", "serviceAccounts") + service_accounts = event.deep_get("protoPayload", "request", "serviceAccounts") if not service_accounts: service_account_emails = "" else: diff --git a/rules/gcp_audit_rules/gcp_destructive_queries.py b/rules/gcp_audit_rules/gcp_destructive_queries.py index 72ded3214..7c67677e3 100644 --- a/rules/gcp_audit_rules/gcp_destructive_queries.py +++ b/rules/gcp_audit_rules/gcp_destructive_queries.py @@ -1,18 +1,15 @@ -from panther_base_helpers import deep_get - DESTRUCTIVE_STATEMENTS = ["UPDATE", "DELETE", "DROP_TABLE", "ALTER_TABLE", "TRUNCATE_TABLE"] def rule(event): if all( [ - deep_get(event, "resource", "type", default="").startswith( + event.deep_get("resource", "type", default="").startswith( "bigquery" ), - deep_get(event, "protoPayload", "metadata", "jobChange", "job", "jobConfig", "type") + event.deep_get("protoPayload", "metadata", "jobChange", "job", "jobConfig", "type") == "QUERY", - deep_get( - event, + event.deep_get( "protoPayload", "metadata", "jobChange", @@ -27,21 +24,20 @@ def rule(event): ): return True - if deep_get(event, "protoPayload", "metadata", "tableDeletion"): + if event.deep_get("protoPayload", "metadata", "tableDeletion"): return True - if deep_get(event, "protoPayload", "metadata", "datasetDeletion"): + if event.deep_get("protoPayload", "metadata", "datasetDeletion"): return True return False def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - statement = deep_get( - event, + statement = event.deep_get( "protoPayload", "metadata", "jobChange", @@ -51,8 +47,7 @@ def title(event): "statementType", default="", ) - table = deep_get( - event, + table = event.deep_get( "protoPayload", "metadata", "jobChange", @@ -60,14 +55,13 @@ def title(event): "jobConfig", "queryConfig", "destinationTable", - ) or deep_get(event, "protoPayload", "metadata", "resourceName", default="") + ) or event.deep_get("protoPayload", "metadata", "resourceName", default="") return f"GCP: [{actor}] performed a destructive BigQuery [{statement}] query on [{table}]." def alert_context(event): return { - "query": deep_get( - event, + "query": event.deep_get( "protoPayload", "metadata", "jobChange", @@ -77,15 +71,13 @@ def alert_context(event): "query", default="", ), - "actor": deep_get( - event, + "actor": event.deep_get( "protoPayload", "authenticationInfo", "principalEmail", default="", ), - "statement": deep_get( - event, + "statement": event.deep_get( "protoPayload", "metadata", "jobChange", @@ -95,8 +87,7 @@ def alert_context(event): "statementType", default="", ), - "table": deep_get( - event, + "table": event.deep_get( "protoPayload", "metadata", "jobChange", @@ -105,5 +96,5 @@ def alert_context(event): "queryConfig", "destinationTable", ) - or deep_get(event, "protoPayload", "metadata", "resourceName", default=""), + or event.deep_get("protoPayload", "metadata", "resourceName", default=""), } diff --git a/rules/gcp_audit_rules/gcp_dns_zone_modified_or_deleted.py b/rules/gcp_audit_rules/gcp_dns_zone_modified_or_deleted.py index f7c990507..edde9df60 100644 --- a/rules/gcp_audit_rules/gcp_dns_zone_modified_or_deleted.py +++ b/rules/gcp_audit_rules/gcp_dns_zone_modified_or_deleted.py @@ -1,5 +1,4 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get def rule(event): @@ -9,14 +8,14 @@ def rule(event): "dns.managedZones.patch", "dns.managedZones.update", ) - return deep_get(event, "protoPayload", "methodName", default="") in methods + return event.deep_get("protoPayload", "methodName", default="") in methods def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - resource = deep_get(event, "protoPayload", "resourceName", default="") + resource = event.deep_get("protoPayload", "resourceName", default="") return f"[GCP]: [{actor}] modified managed DNS zone [{resource}]" diff --git a/rules/gcp_audit_rules/gcp_firewall_rule_created.py b/rules/gcp_audit_rules/gcp_firewall_rule_created.py index 61155d1bf..80a17c711 100644 --- a/rules/gcp_audit_rules/gcp_firewall_rule_created.py +++ b/rules/gcp_audit_rules/gcp_firewall_rule_created.py @@ -1,27 +1,24 @@ import re from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get def rule(event): method_pattern = r"(?:\w+\.)*v\d\.(?:Firewall\.Create)|(compute\.firewalls\.insert)" - match = re.search(method_pattern, deep_get(event, "protoPayload", "methodName", default="")) + match = re.search(method_pattern, event.deep_get("protoPayload", "methodName", default="")) return match is not None def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - resource = deep_get( - event, + resource = event.deep_get( "protoPayload", "resourceName", default="", ) - resource_id = deep_get( - event, + resource_id = event.deep_get( "resource", "labels", "firewall_rule_id", diff --git a/rules/gcp_audit_rules/gcp_firewall_rule_deleted.py b/rules/gcp_audit_rules/gcp_firewall_rule_deleted.py index 0970fdbff..90eb7001e 100644 --- a/rules/gcp_audit_rules/gcp_firewall_rule_deleted.py +++ b/rules/gcp_audit_rules/gcp_firewall_rule_deleted.py @@ -1,27 +1,24 @@ import re from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get def rule(event): method_pattern = r"(?:\w+\.)*v\d\.(?:Firewall\.Delete)|(compute\.firewalls\.delete)" - match = re.search(method_pattern, deep_get(event, "protoPayload", "methodName", default="")) + match = re.search(method_pattern, event.deep_get("protoPayload", "methodName", default="")) return match is not None def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - resource = deep_get( - event, + resource = event.deep_get( "protoPayload", "resourceName", default="", ) - resource_id = deep_get( - event, + resource_id = event.deep_get( "resource", "labels", "firewall_rule_id", diff --git a/rules/gcp_audit_rules/gcp_firewall_rule_modified.py b/rules/gcp_audit_rules/gcp_firewall_rule_modified.py index 1bf4af4f0..a0eb6b774 100644 --- a/rules/gcp_audit_rules/gcp_firewall_rule_modified.py +++ b/rules/gcp_audit_rules/gcp_firewall_rule_modified.py @@ -1,20 +1,19 @@ import re from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get def rule(event): method_pattern = r"(?:\w+\.)*v\d\.(?:Firewall\.Update)|(compute\.firewalls\.(patch|update))" - match = re.search(method_pattern, deep_get(event, "protoPayload", "methodName", default="")) + match = re.search(method_pattern, event.deep_get("protoPayload", "methodName", default="")) return match is not None def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - resource = deep_get(event, "protoPayload", "resourceName", default="") + resource = event.deep_get("protoPayload", "resourceName", default="") return f"[GCP]: [{actor}] modified firewall rule on [{resource}]" diff --git a/rules/gcp_audit_rules/gcp_gcs_iam_changes.py b/rules/gcp_audit_rules/gcp_gcs_iam_changes.py index 2bc7a0760..9cab1fdcf 100644 --- a/rules/gcp_audit_rules/gcp_gcs_iam_changes.py +++ b/rules/gcp_audit_rules/gcp_gcs_iam_changes.py @@ -1,12 +1,9 @@ -from panther_base_helpers import deep_get - - def rule(event): return ( - deep_get(event, "resource", "type") == "gcs_bucket" - and deep_get(event, "protoPayload", "methodName") == "storage.setIamPermissions" + event.deep_get("resource", "type") == "gcs_bucket" + and event.deep_get("protoPayload", "methodName") == "storage.setIamPermissions" ) def dedup(event): - return deep_get(event, "resource", "labels", "project_id", default="") + return event.deep_get("resource", "labels", "project_id", default="") diff --git a/rules/gcp_audit_rules/gcp_gcs_public.py b/rules/gcp_audit_rules/gcp_gcs_public.py index 28743607d..b76cf7ab9 100644 --- a/rules/gcp_audit_rules/gcp_gcs_public.py +++ b/rules/gcp_audit_rules/gcp_gcs_public.py @@ -5,10 +5,10 @@ def rule(event): - if deep_get(event, "protoPayload", "methodName") != "storage.setIamPermissions": + if event.deep_get("protoPayload", "methodName") != "storage.setIamPermissions": return False - service_data = deep_get(event, "protoPayload", "serviceData") + service_data = event.deep_get("protoPayload", "serviceData") if not service_data: return False @@ -28,6 +28,6 @@ def rule(event): def title(event): return ( f"GCS bucket " - f"[{deep_get(event, 'resource', 'labels', 'bucket_name', default='')}] " + f"[{event.deep_get('resource', 'labels', 'bucket_name', default='')}] " f"made public" ) diff --git a/rules/gcp_audit_rules/gcp_iam_corp_email.py b/rules/gcp_audit_rules/gcp_iam_corp_email.py index 909206817..436df396e 100644 --- a/rules/gcp_audit_rules/gcp_iam_corp_email.py +++ b/rules/gcp_audit_rules/gcp_iam_corp_email.py @@ -2,10 +2,10 @@ def rule(event): - if deep_get(event, "protoPayload", "methodName") != "SetIamPolicy": + if event.deep_get("protoPayload", "methodName") != "SetIamPolicy": return False - service_data = deep_get(event, "protoPayload", "serviceData") + service_data = event.deep_get("protoPayload", "serviceData") if not service_data: return False @@ -25,5 +25,5 @@ def rule(event): def title(event): return ( f"A GCP IAM account has been created with a Gmail email in " - f"{deep_get(event, 'resource', 'labels', 'project_id', default='')}" + f"{event.deep_get('resource', 'labels', 'project_id', default='')}" ) diff --git a/rules/gcp_audit_rules/gcp_iam_custom_role_changes.py b/rules/gcp_audit_rules/gcp_iam_custom_role_changes.py index 17f59d563..bf0c992b2 100644 --- a/rules/gcp_audit_rules/gcp_iam_custom_role_changes.py +++ b/rules/gcp_audit_rules/gcp_iam_custom_role_changes.py @@ -1,5 +1,3 @@ -from panther_base_helpers import deep_get - ROLE_METHODS = { "google.iam.admin.v1.CreateRole", "google.iam.admin.v1.DeleteRole", @@ -9,10 +7,10 @@ def rule(event): return ( - deep_get(event, "resource", "type") == "iam_role" - and deep_get(event, "protoPayload", "methodName") in ROLE_METHODS + event.deep_get("resource", "type") == "iam_role" + and event.deep_get("protoPayload", "methodName") in ROLE_METHODS ) def dedup(event): - return deep_get(event, "resource", "labels", "project_id", default="") + return event.deep_get("resource", "labels", "project_id", default="") diff --git a/rules/gcp_audit_rules/gcp_iam_org_folder_changes.py b/rules/gcp_audit_rules/gcp_iam_org_folder_changes.py index 3943ff40b..031d44420 100644 --- a/rules/gcp_audit_rules/gcp_iam_org_folder_changes.py +++ b/rules/gcp_audit_rules/gcp_iam_org_folder_changes.py @@ -1,11 +1,8 @@ -from panther_base_helpers import deep_get - - def rule(event): # Return True to match the log event and trigger an alert. - logname = deep_get(event, "logName") + logname = event.get("logName") return ( - deep_get(event, "protoPayload", "methodName") == "SetIamPolicy" + event.deep_get("protoPayload", "methodName") == "SetIamPolicy" and (logname.startswith("organizations") or logname.startswith("folder")) and logname.endswith("/logs/cloudaudit.googleapis.com%2Factivity") ) @@ -21,15 +18,15 @@ def title(event): def alert_context(event): return { "actor": event.udm("actor_user"), - "policy_change": deep_get(event, "protoPayload", "serviceData", "policyDelta"), - "caller_ip": deep_get(event, "protoPayload", "requestMetadata", "callerIP"), - "user_agent": deep_get(event, "protoPayload", "requestMetadata", "callerSuppliedUserAgent"), + "policy_change": event.deep_get("protoPayload", "serviceData", "policyDelta"), + "caller_ip": event.deep_get("protoPayload", "requestMetadata", "callerIP"), + "user_agent": event.deep_get("protoPayload", "requestMetadata", "callerSuppliedUserAgent"), } def severity(event): if ( - deep_get(event, "protoPayload", "requestMetadata", "callerSuppliedUserAgent") + event.deep_get("protoPayload", "requestMetadata", "callerSuppliedUserAgent") .lower() .find("terraform") != -1 diff --git a/rules/gcp_audit_rules/gcp_iam_roles_update_privilege_escalation.py b/rules/gcp_audit_rules/gcp_iam_roles_update_privilege_escalation.py index 2b274c5f4..f367a0739 100644 --- a/rules/gcp_audit_rules/gcp_iam_roles_update_privilege_escalation.py +++ b/rules/gcp_audit_rules/gcp_iam_roles_update_privilege_escalation.py @@ -1,9 +1,8 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get, deep_walk def rule(event): - authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + authorization_info = event.deep_walk("protoPayload", "authorizationInfo") if not authorization_info: return False @@ -14,11 +13,11 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - operation = deep_get(event, "protoPayload", "methodName", default="") - project_id = deep_get(event, "resource", "labels", "project_id", default="") + operation = event.deep_get("protoPayload", "methodName", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return f"[GCP]: [{actor}] performed [{operation}] on project [{project_id}]" diff --git a/rules/gcp_audit_rules/gcp_iam_service_account_key_create.py b/rules/gcp_audit_rules/gcp_iam_service_account_key_create.py index d506611bd..7997caf25 100644 --- a/rules/gcp_audit_rules/gcp_iam_service_account_key_create.py +++ b/rules/gcp_audit_rules/gcp_iam_service_account_key_create.py @@ -1,9 +1,8 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get, deep_walk def rule(event): - authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + authorization_info = event.deep_walk("protoPayload", "authorizationInfo") if not authorization_info: return False @@ -17,11 +16,11 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - operation = deep_get(event, "protoPayload", "methodName", default="") - project_id = deep_get(event, "resource", "labels", "project_id", default="") + operation = event.deep_get("protoPayload", "methodName", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return f"[GCP]: [{actor}] performed [{operation}] on project [{project_id}]" diff --git a/rules/gcp_audit_rules/gcp_iam_service_accounts_get_access_token_privilege_escalation.py b/rules/gcp_audit_rules/gcp_iam_service_accounts_get_access_token_privilege_escalation.py index 44b90b4f9..d35bcc1d7 100644 --- a/rules/gcp_audit_rules/gcp_iam_service_accounts_get_access_token_privilege_escalation.py +++ b/rules/gcp_audit_rules/gcp_iam_service_accounts_get_access_token_privilege_escalation.py @@ -1,5 +1,4 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get def rule(event): @@ -18,8 +17,8 @@ def rule(event): def title(event): actor = event.udm("actor_user") - operation = deep_get(event, "protoPayload", "methodName", default="") - project_id = deep_get(event, "resource", "labels", "project_id", default="") + operation = event.deep_get("protoPayload", "methodName", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return f"[GCP]: [{actor}] performed [{operation}] on project [{project_id}]" diff --git a/rules/gcp_audit_rules/gcp_iam_service_accounts_sign_blob.py b/rules/gcp_audit_rules/gcp_iam_service_accounts_sign_blob.py index 74ad13b7a..285ac3b1a 100644 --- a/rules/gcp_audit_rules/gcp_iam_service_accounts_sign_blob.py +++ b/rules/gcp_audit_rules/gcp_iam_service_accounts_sign_blob.py @@ -1,5 +1,4 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get def rule(event): @@ -14,11 +13,11 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - operation = deep_get(event, "protoPayload", "methodName", default="") - project_id = deep_get(event, "resource", "labels", "project_id", default="") + operation = event.deep_get("protoPayload", "methodName", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return f"[GCP]: [{actor}] performed [{operation}] on project [{project_id}]" diff --git a/rules/gcp_audit_rules/gcp_iam_serviceaccounts_signjwt.py b/rules/gcp_audit_rules/gcp_iam_serviceaccounts_signjwt.py index 4662b5e7e..7a5403075 100644 --- a/rules/gcp_audit_rules/gcp_iam_serviceaccounts_signjwt.py +++ b/rules/gcp_audit_rules/gcp_iam_serviceaccounts_signjwt.py @@ -1,12 +1,11 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get, deep_walk def rule(event): - if deep_get(event, "protoPayload", "methodName") != "SignJwt": + if event.deep_get("protoPayload", "methodName") != "SignJwt": return False - authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + authorization_info = event.deep_walk("protoPayload", "authorizationInfo") if not authorization_info: return False @@ -17,18 +16,18 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - operation = deep_get(event, "protoPayload", "methodName", default="") - project_id = deep_get(event, "resource", "labels", "project_id", default="") + operation = event.deep_get("protoPayload", "methodName", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return f"[GCP]: [{actor}] performed [{operation}] on project [{project_id}]" def alert_context(event): context = gcp_alert_context(event) - context["serviceAccountKeyName"] = deep_get( - event, "protoPayload", "authenticationInfo", "serviceAccountKeyName" + context["serviceAccountKeyName"] = event.deep_get( + "protoPayload", "authenticationInfo", "serviceAccountKeyName" ) return context diff --git a/rules/gcp_audit_rules/gcp_log_bucket_or_sink_deleted.py b/rules/gcp_audit_rules/gcp_log_bucket_or_sink_deleted.py index 86979d7ae..e378e3665 100644 --- a/rules/gcp_audit_rules/gcp_log_bucket_or_sink_deleted.py +++ b/rules/gcp_audit_rules/gcp_log_bucket_or_sink_deleted.py @@ -1,22 +1,20 @@ import re from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get, deep_walk def rule(event): - authenticated = deep_walk(event, "protoPayload", "authorizationInfo", "granted", default=False) + authenticated = event.deep_walk("protoPayload", "authorizationInfo", "granted", default=False) method_pattern = r"(?:\w+\.)*v\d\.(?:ConfigServiceV\d\.(?:Delete(Bucket|Sink)))" - match = re.search(method_pattern, deep_get(event, "protoPayload", "methodName", default="")) + match = re.search(method_pattern, event.deep_get("protoPayload", "methodName", default="")) return authenticated and match is not None def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - resource = deep_get( - event, + resource = event.deep_get( "protoPayload", "resourceName", default="", diff --git a/rules/gcp_audit_rules/gcp_logging_settings_modified.py b/rules/gcp_audit_rules/gcp_logging_settings_modified.py index c1c55bb35..053f02618 100644 --- a/rules/gcp_audit_rules/gcp_logging_settings_modified.py +++ b/rules/gcp_audit_rules/gcp_logging_settings_modified.py @@ -1,32 +1,28 @@ -from panther_base_helpers import deep_get - - def rule(event): return all( [ - deep_get(event, "protoPayload", "serviceName", default="") == "logging.googleapis.com", - "Update" in deep_get(event, "protoPayload", "methodName", default=""), + event.deep_get("protoPayload", "serviceName", default="") == "logging.googleapis.com", + "Update" in event.deep_get("protoPayload", "methodName", default=""), ] ) def title(event): - resource = deep_get(event, "protoPayload", "resourceName", default="") - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + resource = event.deep_get("protoPayload", "resourceName", default="") + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) return f"GCP [{resource}] logging settings modified by [{actor}]." def alert_context(event): return { - "resource": deep_get(event, "protoPayload", "resourceName", default=""), - "actor": deep_get( - event, + "resource": event.deep_get("protoPayload", "resourceName", default=""), + "actor": event.deep_get( "protoPayload", "authenticationInfo", "principalEmail", default="", ), - "method": deep_get(event, "protoPayload", "methodName", default=""), + "method": event.deep_get("protoPayload", "methodName", default=""), } diff --git a/rules/gcp_audit_rules/gcp_logging_sink_modified.py b/rules/gcp_audit_rules/gcp_logging_sink_modified.py index fc815ff17..e230a4030 100644 --- a/rules/gcp_audit_rules/gcp_logging_sink_modified.py +++ b/rules/gcp_audit_rules/gcp_logging_sink_modified.py @@ -1,21 +1,19 @@ import re from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get def rule(event): method_pattern = r"(?:\w+\.)*v\d\.(?:ConfigServiceV\d\.(?:UpdateSink))" - match = re.search(method_pattern, deep_get(event, "protoPayload", "methodName", default="")) + match = re.search(method_pattern, event.deep_get("protoPayload", "methodName", default="")) return match is not None def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - resource = deep_get( - event, + resource = event.deep_get( "protoPayload", "resourceName", default="", diff --git a/rules/gcp_audit_rules/gcp_permissions_granted_to_create_or_manage_service_account_key.py b/rules/gcp_audit_rules/gcp_permissions_granted_to_create_or_manage_service_account_key.py index 3e1581245..5c5abff85 100644 --- a/rules/gcp_audit_rules/gcp_permissions_granted_to_create_or_manage_service_account_key.py +++ b/rules/gcp_audit_rules/gcp_permissions_granted_to_create_or_manage_service_account_key.py @@ -1,5 +1,3 @@ -from panther_base_helpers import deep_get, deep_walk - SERVICE_ACCOUNT_MANAGE_ROLES = [ "roles/iam.serviceAccountTokenCreator", "roles/iam.serviceAccountUser", @@ -7,10 +5,9 @@ def rule(event): - if "SetIAMPolicy" in deep_get(event, "protoPayload", "methodName", default=""): - role = deep_walk( - event, - "ProtoPayload", + if "SetIAMPolicy" in event.deep_get("protoPayload", "methodName", default=""): + role = event.deep_walk( + "protoPayload", "serviceData", "policyDelta", "bindingDeltas", @@ -18,9 +15,8 @@ def rule(event): default="", return_val="last", ) - action = deep_walk( - event, - "ProtoPayload", + action = event.deep_walk( + "protoPayload", "serviceData", "policyDelta", "bindingDeltas", @@ -33,11 +29,11 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - target = deep_get(event, "resource", "labels", "email_id") or deep_get( - event, "resource", "labels", "project_id", default="" + target = event.deep_get("resource", "labels", "email_id") or event.deep_get( + "resource", "labels", "project_id", default="" ) return ( f"GCP: [{actor}] granted permissions to create or manage service account keys to [{target}]" @@ -46,6 +42,6 @@ def title(event): def alert_context(event): return { - "resource": deep_get(event, "resource"), - "serviceData": deep_get(event, "protoPayload", "serviceData"), + "resource": event.get("resource"), + "serviceData": event.deep_get("protoPayload", "serviceData"), } diff --git a/rules/gcp_audit_rules/gcp_privilege_escalation_by_deployments_create.py b/rules/gcp_audit_rules/gcp_privilege_escalation_by_deployments_create.py index c88390795..16c15b8d2 100644 --- a/rules/gcp_audit_rules/gcp_privilege_escalation_by_deployments_create.py +++ b/rules/gcp_audit_rules/gcp_privilege_escalation_by_deployments_create.py @@ -1,9 +1,8 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get, deep_walk def rule(event): - authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + authorization_info = event.deep_walk("protoPayload", "authorizationInfo") if not authorization_info: return False @@ -17,11 +16,11 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - operation = deep_get(event, "protoPayload", "methodName", default="") - project_id = deep_get(event, "resource", "labels", "project_id", default="") + operation = event.deep_get("protoPayload", "methodName", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return f"[GCP]: [{actor}] performed [{operation}] on project [{project_id}]" diff --git a/rules/gcp_audit_rules/gcp_service_account_access_denied.py b/rules/gcp_audit_rules/gcp_service_account_access_denied.py index d9b0f0c8e..f40e0044f 100644 --- a/rules/gcp_audit_rules/gcp_service_account_access_denied.py +++ b/rules/gcp_audit_rules/gcp_service_account_access_denied.py @@ -1,15 +1,14 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_walk def rule(event): - reason = deep_walk(event, "protoPayload", "status", "details", "reason", default="") + reason = event.deep_walk("protoPayload", "status", "details", "reason", default="") return reason == "IAM_PERMISSION_DENIED" def title(event): - actor = deep_walk( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_walk( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) return f"[GCP]: [{actor}] performed multiple requests resulting in [IAM_PERMISSION_DENIED]" diff --git a/rules/gcp_audit_rules/gcp_service_account_or_keys_created.py b/rules/gcp_audit_rules/gcp_service_account_or_keys_created.py index 49e466345..e19fd9ea1 100644 --- a/rules/gcp_audit_rules/gcp_service_account_or_keys_created.py +++ b/rules/gcp_audit_rules/gcp_service_account_or_keys_created.py @@ -1,27 +1,24 @@ -from panther_base_helpers import deep_get - - def rule(event): return all( [ - deep_get(event, "resource", "type", default="") == "service_account", - "CreateServiceAccount" in deep_get(event, "protoPayload", "methodName", default=""), - not deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + event.deep_get("resource", "type", default="") == "service_account", + "CreateServiceAccount" in event.deep_get("protoPayload", "methodName", default=""), + not event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ).endswith(".gserviceaccount.com"), ] ) def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - target = deep_get(event, "resource", "labels", "email_id") - project = deep_get(event, "resource", "labels", "project_id") + target = event.deep_get("resource", "labels", "email_id") + project = event.deep_get("resource", "labels", "project_id") resource = ( "Service Account Key for" - if deep_get(event, "protoPayload", "methodName", default="") + if event.deep_get("protoPayload", "methodName", default="") == "google.iam.admin.v1.CreateServiceAccountKey" else "Service Account" ) diff --git a/rules/gcp_audit_rules/gcp_serviceusage_apikeys_create_privilege_escalation.py b/rules/gcp_audit_rules/gcp_serviceusage_apikeys_create_privilege_escalation.py index 5ce656f63..1380e3e73 100644 --- a/rules/gcp_audit_rules/gcp_serviceusage_apikeys_create_privilege_escalation.py +++ b/rules/gcp_audit_rules/gcp_serviceusage_apikeys_create_privilege_escalation.py @@ -1,14 +1,13 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get, deep_walk def rule(event): - if not deep_get(event, "protoPayload", "methodName", default="METHOD_NOT_FOUND").endswith( + if not event.deep_get("protoPayload", "methodName", default="METHOD_NOT_FOUND").endswith( "ApiKeys.CreateKey" ): return False - authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + authorization_info = event.deep_walk("protoPayload", "authorizationInfo") if not authorization_info: return False @@ -19,10 +18,10 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - project_id = deep_get(event, "resource", "labels", "project_id", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return f"[GCP]: [{actor}] created new API Key in project [{project_id}]" diff --git a/rules/gcp_audit_rules/gcp_sql_config_changes.py b/rules/gcp_audit_rules/gcp_sql_config_changes.py index d8b961347..0cff312c4 100644 --- a/rules/gcp_audit_rules/gcp_sql_config_changes.py +++ b/rules/gcp_audit_rules/gcp_sql_config_changes.py @@ -1,9 +1,6 @@ -from panther_base_helpers import deep_get - - def rule(event): - return deep_get(event, "protoPayload", "methodName") == "cloudsql.instances.update" + return event.deep_get("protoPayload", "methodName") == "cloudsql.instances.update" def dedup(event): - return deep_get(event, "resource", "labels", "project_id", default="") + return event.deep_get("resource", "labels", "project_id", default="") diff --git a/rules/gcp_audit_rules/gcp_unused_regions.py b/rules/gcp_audit_rules/gcp_unused_regions.py index 15ec7ba68..9b1518b11 100644 --- a/rules/gcp_audit_rules/gcp_unused_regions.py +++ b/rules/gcp_audit_rules/gcp_unused_regions.py @@ -38,7 +38,7 @@ def _get_location_or_zone(event): def rule(event): - method_name = deep_get(event, "protoPayload", "methodName", default="") + method_name = event.deep_get("protoPayload", "methodName", default="") if not method_name.endswith(("insert", "create")): return False return _resource_in_active_region(_get_location_or_zone(event)) @@ -47,5 +47,5 @@ def rule(event): def title(event): return ( f"GCP resource(s) created in unused region/zone in project " - f"{deep_get(event, 'resource', 'labels', 'project_id', default='')}" + f"{event.deep_get('resource', 'labels', 'project_id', default='')}" ) diff --git a/rules/gcp_audit_rules/gcp_user_added_to_iap_protected_service.py b/rules/gcp_audit_rules/gcp_user_added_to_iap_protected_service.py index 164394b12..c0d1ac130 100644 --- a/rules/gcp_audit_rules/gcp_user_added_to_iap_protected_service.py +++ b/rules/gcp_audit_rules/gcp_user_added_to_iap_protected_service.py @@ -1,22 +1,19 @@ -from panther_base_helpers import deep_get - - def rule(event): return ( - deep_get(event, "protoPayload", "methodName", default="") + event.deep_get("protoPayload", "methodName", default="") == "google.cloud.iap.v1.IdentityAwareProxyAdminService.SetIamPolicy" ) def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - service = deep_get(event, "protoPayload", "request", "resource", default="") + service = event.deep_get("protoPayload", "request", "resource", default="") return f"GCP: [{actor}] modified user access to IAP Protected Service [{service}]" def alert_context(event): - bindings = deep_get(event, "protoPayload", "request", "policy", "bindings", default=[{}]) + bindings = event.deep_get("protoPayload", "request", "policy", "bindings", default=[{}]) return {"bindings": bindings} diff --git a/rules/gcp_audit_rules/gcp_vpc_flow_logs_disabled.py b/rules/gcp_audit_rules/gcp_vpc_flow_logs_disabled.py index 159b51783..3933b9806 100644 --- a/rules/gcp_audit_rules/gcp_vpc_flow_logs_disabled.py +++ b/rules/gcp_audit_rules/gcp_vpc_flow_logs_disabled.py @@ -1,20 +1,17 @@ -from panther_base_helpers import deep_get - - def rule(event): return all( [ event.get("protoPayload"), - deep_get(event, "protoPayload", "methodName", default="") + event.deep_get("protoPayload", "methodName", default="") == "v1.compute.subnetworks.patch", - deep_get(event, "protoPayload", "request", "enableFlowLogs") is False, + event.deep_get("protoPayload", "request", "enableFlowLogs") is False, ] ) def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - resource = deep_get(event, "protoPayload", "resourceName", default="") + resource = event.deep_get("protoPayload", "resourceName", default="") return f"GCP: [{actor}] disabled VPC Flow Logs for [{resource}]" diff --git a/rules/gcp_audit_rules/gcp_workforce_pool_created_or_updated.py b/rules/gcp_audit_rules/gcp_workforce_pool_created_or_updated.py index 77df5fba9..342aaf198 100644 --- a/rules/gcp_audit_rules/gcp_workforce_pool_created_or_updated.py +++ b/rules/gcp_audit_rules/gcp_workforce_pool_created_or_updated.py @@ -16,9 +16,7 @@ def title(event): "protoPayload", "request", "workforcePool", "name", default="" ).split("/")[-1] - resource = organization_id = event.deep_get("logName", default="").split( - "/" - ) + resource = organization_id = event.get("logName", "").split("/") organization_id = resource[resource.index("organizations") + 1] diff --git a/rules/gcp_http_lb_rules/gcp_access_attempts_violating_iap_access_controls.py b/rules/gcp_http_lb_rules/gcp_access_attempts_violating_iap_access_controls.py index cbdf74d27..9c31afc41 100644 --- a/rules/gcp_http_lb_rules/gcp_access_attempts_violating_iap_access_controls.py +++ b/rules/gcp_http_lb_rules/gcp_access_attempts_violating_iap_access_controls.py @@ -1,16 +1,13 @@ -from panther_base_helpers import deep_get - - def rule(event): return all( [ - deep_get(event, "resource", "type", default="") == "http_load_balancer", - deep_get(event, "jsonPayload", "statusDetails", default="") + event.deep_get("resource", "type", default="") == "http_load_balancer", + event.deep_get("jsonPayload", "statusDetails", default="") == "handled_by_identity_aware_proxy", not any( [ - str(deep_get(event, "httprequest", "status", default=000)).startswith("2"), - str(deep_get(event, "httprequest", "status", default=000)).startswith("3"), + str(event.deep_get("httprequest", "status", default=000)).startswith("2"), + str(event.deep_get("httprequest", "status", default=000)).startswith("3"), ] ), ] @@ -18,6 +15,6 @@ def rule(event): def title(event): - source = deep_get(event, "jsonPayload", "remoteIp", default="") - request_url = deep_get(event, "httprequest", "requestUrl", default="") + source = event.deep_get("jsonPayload", "remoteIp", default="") + request_url = event.deep_get("httprequest", "requestUrl", default="") return f"GCP: Request Violating IAP controls from [{source}] to [{request_url}]" diff --git a/rules/gcp_k8s_rules/gcp_k8s_cron_job_created_or_modified.py b/rules/gcp_k8s_rules/gcp_k8s_cron_job_created_or_modified.py index 81668e586..4c0b5e7ad 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_cron_job_created_or_modified.py +++ b/rules/gcp_k8s_rules/gcp_k8s_cron_job_created_or_modified.py @@ -1,9 +1,8 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get, deep_walk def rule(event): - authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + authorization_info = event.deep_walk("protoPayload", "authorizationInfo") if not authorization_info: return False for auth in authorization_info: @@ -17,11 +16,11 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - operation = deep_get(event, "protoPayload", "methodName", default="") - project_id = deep_get(event, "resource", "labels", "project_id", default="") + operation = event.deep_get("protoPayload", "methodName", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return f"[GCP]: [{actor}] performed [{operation}] on project [{project_id}]" diff --git a/rules/gcp_k8s_rules/gcp_k8s_exec_into_pod.py b/rules/gcp_k8s_rules/gcp_k8s_exec_into_pod.py index 6e4ff324b..19329a987 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_exec_into_pod.py +++ b/rules/gcp_k8s_rules/gcp_k8s_exec_into_pod.py @@ -7,8 +7,8 @@ def rule(event): # Defaults to False (no alert) unless method is exec and principal not allowed if not all( [ - deep_walk(event, "protoPayload", "methodName") == "io.k8s.core.v1.pods.exec.create", - deep_walk(event, "resource", "type") == "k8s_cluster", + event.deep_walk("protoPayload", "methodName") == "io.k8s.core.v1.pods.exec.create", + event.deep_walk("resource", "type") == "k8s_cluster", ] ): return False diff --git a/rules/gcp_k8s_rules/gcp_k8s_ioc_activity.py b/rules/gcp_k8s_rules/gcp_k8s_ioc_activity.py index 24abdffa3..26c42242d 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_ioc_activity.py +++ b/rules/gcp_k8s_rules/gcp_k8s_ioc_activity.py @@ -1,26 +1,25 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get def rule(event): - if deep_get(event, "operation", "producer") == "k8s.io" and deep_get( - event, "p_enrichment", "tor_exit_nodes" + if event.deep_get("operation", "producer") == "k8s.io" and event.deep_get( + "p_enrichment", "tor_exit_nodes" ): return True return False def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - operation = deep_get(event, "protoPayload", "methodName", default="") - project_id = deep_get(event, "resource", "labels", "project_id", default="") + operation = event.deep_get("protoPayload", "methodName", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return f"[GCP]: [{actor}] performed [{operation}] on project [{project_id}]" def alert_context(event): context = gcp_alert_context(event) - context["tor_exit_nodes"] = deep_get(event, "p_enrichment", "tor_exit_nodes") + context["tor_exit_nodes"] = event.deep_get("p_enrichment", "tor_exit_nodes") return context diff --git a/rules/gcp_k8s_rules/gcp_k8s_new_daemonset_deployed.py b/rules/gcp_k8s_rules/gcp_k8s_new_daemonset_deployed.py index 6d926e044..ee6b070ec 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_new_daemonset_deployed.py +++ b/rules/gcp_k8s_rules/gcp_k8s_new_daemonset_deployed.py @@ -1,9 +1,8 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get, deep_walk def rule(event): - authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + authorization_info = event.deep_walk("protoPayload", "authorizationInfo") if not authorization_info: return False for auth in authorization_info: @@ -16,11 +15,11 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - operation = deep_get(event, "protoPayload", "methodName", default="") - project_id = deep_get(event, "resource", "labels", "project_id", default="") + operation = event.deep_get("protoPayload", "methodName", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return f"[GCP]: [{actor}] performed [{operation}] on project [{project_id}]" diff --git a/rules/gcp_k8s_rules/gcp_k8s_pod_attached_to_node_host_network.py b/rules/gcp_k8s_rules/gcp_k8s_pod_attached_to_node_host_network.py index bcb43d540..d505c524f 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_pod_attached_to_node_host_network.py +++ b/rules/gcp_k8s_rules/gcp_k8s_pod_attached_to_node_host_network.py @@ -1,16 +1,15 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get, deep_walk def rule(event): - if deep_get(event, "protoPayload", "methodName") not in ( + if event.deep_get("protoPayload", "methodName") not in ( "io.k8s.core.v1.pods.create", "io.k8s.core.v1.pods.update", "io.k8s.core.v1.pods.patch", ): return False - host_network = deep_walk(event, "protoPayload", "request", "spec", "hostNetwork") + host_network = event.deep_walk("protoPayload", "request", "spec", "hostNetwork") if host_network is not True: return False @@ -18,10 +17,10 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - project_id = deep_get(event, "resource", "labels", "project_id", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return ( f"[GCP]: [{actor}] created or modified pod which is attached to the host's network " diff --git a/rules/gcp_k8s_rules/gcp_k8s_pod_create_or_modify_host_path_vol_mount.py b/rules/gcp_k8s_rules/gcp_k8s_pod_create_or_modify_host_path_vol_mount.py index a31cd58c2..3ab845673 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_pod_create_or_modify_host_path_vol_mount.py +++ b/rules/gcp_k8s_rules/gcp_k8s_pod_create_or_modify_host_path_vol_mount.py @@ -1,5 +1,4 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get, deep_walk SUSPICIOUS_PATHS = [ "/var/run/docker.sock", @@ -15,18 +14,18 @@ def rule(event): - if deep_get(event, "protoPayload", "response", "status") == "Failure": + if event.deep_get("protoPayload", "response", "status") == "Failure": return False - if deep_get(event, "protoPayload", "methodName") not in ( + if event.deep_get("protoPayload", "methodName") not in ( "io.k8s.core.v1.pods.create", "io.k8s.core.v1.pods.update", "io.k8s.core.v1.pods.patch", ): return False - volume_mount_path = deep_walk( - event, "protoPayload", "request", "spec", "volumes", "hostPath", "path" + volume_mount_path = event.deep_walk( + "protoPayload", "request", "spec", "volumes", "hostPath", "path" ) if ( @@ -36,7 +35,7 @@ def rule(event): ): return False - authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + authorization_info = event.deep_walk("protoPayload", "authorizationInfo") if not authorization_info: return False @@ -55,11 +54,11 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - pod_name = deep_get(event, "protoPayload", "resourceName", default="") - project_id = deep_get(event, "resource", "labels", "project_id", default="") + pod_name = event.deep_get("protoPayload", "resourceName", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return ( f"[GCP]: [{actor}] created k8s pod [{pod_name}] with a hostPath volume mount " @@ -68,15 +67,15 @@ def title(event): def dedup(event): - return deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + return event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) def alert_context(event): context = gcp_alert_context(event) - volume_mount_path = deep_walk( - event, "protoPayload", "request", "spec", "volumes", "hostPath", "path" + volume_mount_path = event.deep_walk( + "protoPayload", "request", "spec", "volumes", "hostPath", "path" ) context["volume_mount_path"] = volume_mount_path return context diff --git a/rules/gcp_k8s_rules/gcp_k8s_pod_using_host_pid_namespace.py b/rules/gcp_k8s_rules/gcp_k8s_pod_using_host_pid_namespace.py index 1feefccf0..f40254b72 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_pod_using_host_pid_namespace.py +++ b/rules/gcp_k8s_rules/gcp_k8s_pod_using_host_pid_namespace.py @@ -1,5 +1,4 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get METHODS_TO_CHECK = [ "io.k8s.core.v1.pods.create", @@ -9,19 +8,19 @@ def rule(event): - method = deep_get(event, "protoPayload", "methodName") - request_host_pid = deep_get(event, "protoPayload", "request", "spec", "hostPID") - response_host_pid = deep_get(event, "protoPayload", "responce", "spec", "hostPID") + method = event.deep_get("protoPayload", "methodName") + request_host_pid = event.deep_get("protoPayload", "request", "spec", "hostPID") + response_host_pid = event.deep_get("protoPayload", "responce", "spec", "hostPID") if (request_host_pid is True or response_host_pid is True) and method in METHODS_TO_CHECK: return True return False def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - project_id = deep_get(event, "resource", "labels", "project_id", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return ( f"[GCP]: [{actor}] created or modified pod using the host PID namespace " diff --git a/rules/gcp_k8s_rules/gcp_k8s_privileged_pod_created.py b/rules/gcp_k8s_rules/gcp_k8s_privileged_pod_created.py index 4ab533e38..d626f87c7 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_privileged_pod_created.py +++ b/rules/gcp_k8s_rules/gcp_k8s_privileged_pod_created.py @@ -1,18 +1,18 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get, deep_walk +from panther_base_helpers import deep_get def rule(event): - if deep_get(event, "protoPayload", "response", "status") == "Failure": + if event.deep_get("protoPayload", "response", "status") == "Failure": return False - if deep_get(event, "protoPayload", "methodName") != "io.k8s.core.v1.pods.create": + if event.deep_get("protoPayload", "methodName") != "io.k8s.core.v1.pods.create": return False - authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + authorization_info = event.deep_walk("protoPayload", "authorizationInfo") if not authorization_info: return False - containers_info = deep_walk(event, "protoPayload", "response", "spec", "containers") + containers_info = event.deep_walk("protoPayload", "response", "spec", "containers") for auth in authorization_info: if auth.get("permission") == "io.k8s.core.v1.pods.create" and auth.get("granted") is True: for security_context in containers_info: @@ -26,23 +26,23 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - pod_name = deep_get(event, "protoPayload", "resourceName", default="") - project_id = deep_get(event, "resource", "labels", "project_id", default="") + pod_name = event.deep_get("protoPayload", "resourceName", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return f"[GCP]: [{actor}] created a privileged pod [{pod_name}] in project [{project_id}]" def dedup(event): - return deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + return event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) def alert_context(event): context = gcp_alert_context(event) - containers_info = deep_walk(event, "protoPayload", "response", "spec", "containers", default=[]) + containers_info = event.deep_walk("protoPayload", "response", "spec", "containers", default=[]) context["pod_security_context"] = [i.get("securityContext") for i in containers_info] return context diff --git a/rules/gcp_k8s_rules/gcp_k8s_service_type_node_port_deployed.py b/rules/gcp_k8s_rules/gcp_k8s_service_type_node_port_deployed.py index 8db2e21ee..9e85761c9 100644 --- a/rules/gcp_k8s_rules/gcp_k8s_service_type_node_port_deployed.py +++ b/rules/gcp_k8s_rules/gcp_k8s_service_type_node_port_deployed.py @@ -1,18 +1,17 @@ from gcp_base_helpers import gcp_alert_context -from panther_base_helpers import deep_get, deep_walk def rule(event): - if deep_get(event, "protoPayload", "response", "status") == "Failure": + if event.deep_get("protoPayload", "response", "status") == "Failure": return False - if deep_get(event, "protoPayload", "methodName") != "io.k8s.core.v1.services.create": + if event.deep_get("protoPayload", "methodName") != "io.k8s.core.v1.services.create": return False - if deep_get(event, "protoPayload", "request", "spec", "type") != "NodePort": + if event.deep_get("protoPayload", "request", "spec", "type") != "NodePort": return False - authorization_info = deep_walk(event, "protoPayload", "authorizationInfo") + authorization_info = event.deep_walk("protoPayload", "authorizationInfo") if not authorization_info: return False @@ -26,16 +25,16 @@ def rule(event): def title(event): - actor = deep_get( - event, "protoPayload", "authenticationInfo", "principalEmail", default="" + actor = event.deep_get( + "protoPayload", "authenticationInfo", "principalEmail", default="" ) - project_id = deep_get(event, "resource", "labels", "project_id", default="") + project_id = event.deep_get("resource", "labels", "project_id", default="") return f"[GCP]: [{actor}] created NodePort service in project [{project_id}]" def alert_context(event): context = gcp_alert_context(event) - request_spec = deep_walk(event, "protoPayload", "request", "spec") + request_spec = event.deep_walk("protoPayload", "request", "spec") context["request_spec"] = request_spec return context diff --git a/rules/github_rules/github_webhook_modified.py b/rules/github_rules/github_webhook_modified.py index 928c5b2de..18ce1f259 100644 --- a/rules/github_rules/github_webhook_modified.py +++ b/rules/github_rules/github_webhook_modified.py @@ -1,5 +1,5 @@ from global_filter_github import filter_include_event -from panther_base_helpers import deep_get, github_alert_context +from panther_base_helpers import github_alert_context def rule(event): @@ -17,7 +17,7 @@ def title(event): action = "created" title_str = ( - f"Github webhook [{deep_get(event,'config','url',default='')}]" + f"Github webhook [{event.deep_get('config','url',default='')}]" f" {action} by [{event.get('actor','')}]" ) if repo != "": @@ -37,5 +37,5 @@ def alert_context(event): ctx["hook_id"] = event.get("hook_id", "") ctx["integration"] = event.get("integration", "") ctx["operation_type"] = event.get("operation_type", "") - ctx["url"] = deep_get(event, "config", "url", default="") + ctx["url"] = event.deep_get("config", "url", default="") return ctx diff --git a/rules/gitlab_rules/gitlab_production_password_reset_multiple_emails.py b/rules/gitlab_rules/gitlab_production_password_reset_multiple_emails.py index e1f22a484..44669fea1 100644 --- a/rules/gitlab_rules/gitlab_production_password_reset_multiple_emails.py +++ b/rules/gitlab_rules/gitlab_production_password_reset_multiple_emails.py @@ -3,12 +3,12 @@ def rule(event): - path = event.get("path", default="") + path = event.get("path", "") if path != "/users/password": return False - params = event.get("params", default=[]) + params = event.get("params", []) for param in params: if param.get("key") == "user": email = deep_get(param, "value", "email", default=[]) diff --git a/rules/gsuite_activityevent_rules/google_workspace_advanced_protection_program.py b/rules/gsuite_activityevent_rules/google_workspace_advanced_protection_program.py index 1c5cceddb..88537ed6a 100644 --- a/rules/gsuite_activityevent_rules/google_workspace_advanced_protection_program.py +++ b/rules/gsuite_activityevent_rules/google_workspace_advanced_protection_program.py @@ -1,10 +1,7 @@ -from panther_base_helpers import deep_get - - def rule(event): # Return True to match the log event and trigger an alert. setting_name = ( - deep_get(event, "parameters", "SETTING_NAME", default="NO_SETTING_NAME") + event.deep_get("parameters", "SETTING_NAME", default="NO_SETTING_NAME") .split("-")[0] .strip() ) diff --git a/rules/gsuite_activityevent_rules/google_workspace_apps_marketplace_allowlist.py b/rules/gsuite_activityevent_rules/google_workspace_apps_marketplace_allowlist.py index ac74286e9..d50fd70e4 100644 --- a/rules/gsuite_activityevent_rules/google_workspace_apps_marketplace_allowlist.py +++ b/rules/gsuite_activityevent_rules/google_workspace_apps_marketplace_allowlist.py @@ -1,11 +1,8 @@ -from panther_base_helpers import deep_get - - def rule(event): # Return True to match the log event and trigger an alert. - setting_name = deep_get(event, "parameters", "SETTING_NAME", default="") - old_val = deep_get(event, "parameters", "OLD_VALUE", default="") - new_val = deep_get(event, "parameters", "NEW_VALUE", default="") + setting_name = event.deep_get("parameters", "SETTING_NAME", default="") + old_val = event.deep_get("parameters", "OLD_VALUE", default="") + new_val = event.deep_get("parameters", "NEW_VALUE", default="") return setting_name == "ENABLE_G_SUITE_MARKETPLACE" and old_val != new_val @@ -19,9 +16,9 @@ def title(event): "2": "Allow users to install and run any app from the Marketplace", "3": "Allow users to install and run only selected apps from the Marketplace", } - old_val = deep_get(event, "parameters", "OLD_VALUE", default="") - new_val = deep_get(event, "parameters", "NEW_VALUE", default="") - actor = deep_get(event, "actor", "email", default="") + old_val = event.deep_get("parameters", "OLD_VALUE", default="") + new_val = event.deep_get("parameters", "NEW_VALUE", default="") + actor = event.deep_get("actor", "email", default="") return ( f"Google Workspace User [{actor}] " f"made an application allowlist setting change from [{value_dict.get(str(old_val))}] " diff --git a/rules/gsuite_activityevent_rules/gsuite_advanced_protection.py b/rules/gsuite_activityevent_rules/gsuite_advanced_protection.py index 5da1bb109..c4054eaa9 100644 --- a/rules/gsuite_activityevent_rules/gsuite_advanced_protection.py +++ b/rules/gsuite_activityevent_rules/gsuite_advanced_protection.py @@ -1,8 +1,5 @@ -from panther_base_helpers import deep_get - - def rule(event): - if deep_get(event, "id", "applicationName") != "user_accounts": + if event.deep_get("id", "applicationName") != "user_accounts": return False return bool(event.get("name") == "titanium_unenroll") @@ -11,5 +8,5 @@ def rule(event): def title(event): return ( f"Advanced protection was disabled for user " - f"[{deep_get(event, 'actor', 'email', default='')}]" + f"[{event.deep_get('actor', 'email', default='')}]" ) diff --git a/rules/gsuite_activityevent_rules/gsuite_calendar_made_public.py b/rules/gsuite_activityevent_rules/gsuite_calendar_made_public.py index ef96aa053..eef4441ba 100644 --- a/rules/gsuite_activityevent_rules/gsuite_calendar_made_public.py +++ b/rules/gsuite_activityevent_rules/gsuite_calendar_made_public.py @@ -1,6 +1,3 @@ -from panther_base_helpers import deep_get - - def rule(event): return ( event.get("name") == "change_calendar_acls" @@ -12,9 +9,9 @@ def rule(event): def title(event): return ( f"GSuite calendar " - f"[{deep_get(event, 'parameters', 'calendar_id', default='')}] made " + f"[{event.deep_get('parameters', 'calendar_id', default='')}] made " f"{public_or_private(event)} by " - f"[{deep_get(event, 'actor', 'email', default='')}]" + f"[{event.deep_get('actor', 'email', default='')}]" ) @@ -23,4 +20,4 @@ def severity(event): def public_or_private(event): - return "private" if deep_get(event, "parameters", "access_level") == "none" else "public" + return "private" if event.deep_get("parameters", "access_level") == "none" else "public" diff --git a/rules/gsuite_activityevent_rules/gsuite_doc_ownership_transfer.py b/rules/gsuite_activityevent_rules/gsuite_doc_ownership_transfer.py index 6aa5e9920..1910b1ef6 100644 --- a/rules/gsuite_activityevent_rules/gsuite_doc_ownership_transfer.py +++ b/rules/gsuite_activityevent_rules/gsuite_doc_ownership_transfer.py @@ -1,4 +1,3 @@ -from panther_base_helpers import deep_get from panther_config import config GSUITE_TRUSTED_OWNERSHIP_DOMAINS = { @@ -7,11 +6,11 @@ def rule(event): - if deep_get(event, "id", "applicationName") != "admin": + if event.deep_get("id", "applicationName") != "admin": return False if bool(event.get("name") == "TRANSFER_DOCUMENT_OWNERSHIP"): - new_owner = deep_get(event, "parameters", "NEW_VALUE", default="") + new_owner = event.deep_get("parameters", "NEW_VALUE", default="") return bool(new_owner) and not any( new_owner.endswith(x) for x in GSUITE_TRUSTED_OWNERSHIP_DOMAINS ) diff --git a/rules/gsuite_activityevent_rules/gsuite_external_forwarding.py b/rules/gsuite_activityevent_rules/gsuite_external_forwarding.py index 0b2306c6b..c7f3b0c8c 100644 --- a/rules/gsuite_activityevent_rules/gsuite_external_forwarding.py +++ b/rules/gsuite_activityevent_rules/gsuite_external_forwarding.py @@ -1,15 +1,12 @@ -from panther_base_helpers import deep_get from panther_config import config def rule(event): - if deep_get(event, "id", "applicationName") != "user_accounts": + if event.deep_get("id", "applicationName") != "user_accounts": return False if event.get("name") == "email_forwarding_out_of_domain": - domain = deep_get(event, "parameters", "email_forwarding_destination_address").split("@")[ - -1 - ] + domain = event.deep_get("parameters", "email_forwarding_destination_address").split("@")[-1] if domain not in config.GSUITE_TRUSTED_FORWARDING_DESTINATION_DOMAINS: return True @@ -17,7 +14,7 @@ def rule(event): def title(event): - external_address = deep_get(event, "parameters", "email_forwarding_destination_address") - user = deep_get(event, "actor", "email") + external_address = event.deep_get("parameters", "email_forwarding_destination_address") + user = event.deep_get("actor", "email") return f"An email forwarding rule was created by {user} to {external_address}" diff --git a/rules/gsuite_activityevent_rules/gsuite_google_access.py b/rules/gsuite_activityevent_rules/gsuite_google_access.py index 7c862d76e..28a0eea48 100644 --- a/rules/gsuite_activityevent_rules/gsuite_google_access.py +++ b/rules/gsuite_activityevent_rules/gsuite_google_access.py @@ -1,8 +1,5 @@ -from panther_base_helpers import deep_get - - def rule(event): - if deep_get(event, "id", "applicationName") != "access_transparency": + if event.deep_get("id", "applicationName") != "access_transparency": return False return bool(event.get("type") == "GSUITE_RESOURCE") diff --git a/rules/gsuite_activityevent_rules/gsuite_gov_attack.py b/rules/gsuite_activityevent_rules/gsuite_gov_attack.py index 3213ebf78..92a09f276 100644 --- a/rules/gsuite_activityevent_rules/gsuite_gov_attack.py +++ b/rules/gsuite_activityevent_rules/gsuite_gov_attack.py @@ -1,8 +1,5 @@ -from panther_base_helpers import deep_get - - def rule(event): - if deep_get(event, "id", "applicationName") != "login": + if event.deep_get("id", "applicationName") != "login": return False return bool(event.get("name") == "gov_attack_warning") @@ -10,6 +7,6 @@ def rule(event): def title(event): return ( - f"User [{deep_get(event, 'actor', 'email', default='')}] may have been " + f"User [{event.deep_get('actor', 'email', default='')}] may have been " f"targeted by a government attack" ) diff --git a/rules/gsuite_activityevent_rules/gsuite_group_banned_user.py b/rules/gsuite_activityevent_rules/gsuite_group_banned_user.py index d74a16506..39a1f18a3 100644 --- a/rules/gsuite_activityevent_rules/gsuite_group_banned_user.py +++ b/rules/gsuite_activityevent_rules/gsuite_group_banned_user.py @@ -1,8 +1,5 @@ -from panther_base_helpers import deep_get - - def rule(event): - if deep_get(event, "id", "applicationName") != "groups_enterprise": + if event.deep_get("id", "applicationName") != "groups_enterprise": return False if event.get("type") == "moderator_action": @@ -13,6 +10,6 @@ def rule(event): def title(event): return ( - f"User [{deep_get(event, 'actor', 'email', default='')}] " + f"User [{event.deep_get('actor', 'email', default='')}] " f"banned another user from a group." ) diff --git a/rules/gsuite_activityevent_rules/gsuite_leaked_password.py b/rules/gsuite_activityevent_rules/gsuite_leaked_password.py index 7dac0ef9e..39f5b26ab 100644 --- a/rules/gsuite_activityevent_rules/gsuite_leaked_password.py +++ b/rules/gsuite_activityevent_rules/gsuite_leaked_password.py @@ -1,12 +1,10 @@ -from panther_base_helpers import deep_get - PASSWORD_LEAKED_EVENTS = { "account_disabled_password_leak", } def rule(event): - if deep_get(event, "id", "applicationName") != "login": + if event.deep_get("id", "applicationName") != "login": return False if event.get("type") == "account_warning": @@ -15,7 +13,7 @@ def rule(event): def title(event): - user = deep_get(event, "parameters", "affected_email_address") + user = event.deep_get("parameters", "affected_email_address") if not user: user = "" return f"User [{user}]'s account was disabled due to a password leak" diff --git a/rules/gsuite_activityevent_rules/gsuite_login_type.py b/rules/gsuite_activityevent_rules/gsuite_login_type.py index b9aa7d2bc..51405b22e 100644 --- a/rules/gsuite_activityevent_rules/gsuite_login_type.py +++ b/rules/gsuite_activityevent_rules/gsuite_login_type.py @@ -1,5 +1,3 @@ -from panther_base_helpers import deep_get - # allow-list of approved login types APPROVED_LOGIN_TYPES = { "exchange", @@ -21,8 +19,8 @@ def rule(event): return False if ( - deep_get(event, "parameters", "login_type") in APPROVED_LOGIN_TYPES - or deep_get(event, "id", "applicationName") in APPROVED_APPLICATION_NAMES + event.deep_get("parameters", "login_type") in APPROVED_LOGIN_TYPES + or event.deep_get("id", "applicationName") in APPROVED_APPLICATION_NAMES ): return False @@ -32,5 +30,5 @@ def rule(event): def title(event): return ( f"A login attempt of a non-approved type was detected for user " - f"[{deep_get(event, 'actor', 'email', default='')}]" + f"[{event.deep_get('actor', 'email', default='')}]" ) diff --git a/rules/gsuite_activityevent_rules/gsuite_mobile_device_compromise.py b/rules/gsuite_activityevent_rules/gsuite_mobile_device_compromise.py index 9d909b54a..1d4f60bde 100644 --- a/rules/gsuite_activityevent_rules/gsuite_mobile_device_compromise.py +++ b/rules/gsuite_activityevent_rules/gsuite_mobile_device_compromise.py @@ -1,18 +1,15 @@ -from panther_base_helpers import deep_get - - def rule(event): - if deep_get(event, "id", "applicationName") != "mobile": + if event.deep_get("id", "applicationName") != "mobile": return False if event.get("name") == "DEVICE_COMPROMISED_EVENT": - return bool(deep_get(event, "parameters", "DEVICE_COMPROMISED_STATE") == "COMPROMISED") + return bool(event.deep_get("parameters", "DEVICE_COMPROMISED_STATE") == "COMPROMISED") return False def title(event): return ( - f"User [{deep_get(event, 'parameters', 'USER_EMAIL', default='')}]'s " + f"User [{event.deep_get('parameters', 'USER_EMAIL', default='')}]'s " f"device was compromised" ) diff --git a/rules/gsuite_activityevent_rules/gsuite_mobile_device_screen_unlock_fail.py b/rules/gsuite_activityevent_rules/gsuite_mobile_device_screen_unlock_fail.py index aeb9a3803..5f9227cca 100644 --- a/rules/gsuite_activityevent_rules/gsuite_mobile_device_screen_unlock_fail.py +++ b/rules/gsuite_activityevent_rules/gsuite_mobile_device_screen_unlock_fail.py @@ -1,14 +1,12 @@ -from panther_base_helpers import deep_get - MAX_UNLOCK_ATTEMPTS = 10 def rule(event): - if deep_get(event, "id", "applicationName") != "mobile": + if event.deep_get("id", "applicationName") != "mobile": return False if event.get("name") == "FAILED_PASSWORD_ATTEMPTS_EVENT": - attempts = deep_get(event, "parameters", "FAILED_PASSWD_ATTEMPTS") + attempts = event.deep_get("parameters", "FAILED_PASSWD_ATTEMPTS") return int(attempts if attempts else 0) > MAX_UNLOCK_ATTEMPTS return False @@ -16,6 +14,6 @@ def rule(event): def title(event): return ( - f"User [{deep_get(event, 'actor', 'email', default='')}]" + f"User [{event.deep_get('actor', 'email', default='')}]" f"'s device had multiple failed unlock attempts" ) diff --git a/rules/gsuite_activityevent_rules/gsuite_mobile_device_suspicious_activity.py b/rules/gsuite_activityevent_rules/gsuite_mobile_device_suspicious_activity.py index ee9e32768..e12254785 100644 --- a/rules/gsuite_activityevent_rules/gsuite_mobile_device_suspicious_activity.py +++ b/rules/gsuite_activityevent_rules/gsuite_mobile_device_suspicious_activity.py @@ -1,8 +1,5 @@ -from panther_base_helpers import deep_get - - def rule(event): - if deep_get(event, "id", "applicationName") != "mobile": + if event.deep_get("id", "applicationName") != "mobile": return False return bool(event.get("name") == "SUSPICIOUS_ACTIVITY_EVENT") @@ -10,6 +7,6 @@ def rule(event): def title(event): return ( - f"User [{deep_get(event, 'actor', 'email', default='')}]" + f"User [{event.deep_get('actor', 'email', default='')}]" f"'s device was compromised" ) diff --git a/rules/gsuite_activityevent_rules/gsuite_passthrough_rule.py b/rules/gsuite_activityevent_rules/gsuite_passthrough_rule.py index 33b0eee09..2018afb62 100644 --- a/rules/gsuite_activityevent_rules/gsuite_passthrough_rule.py +++ b/rules/gsuite_activityevent_rules/gsuite_passthrough_rule.py @@ -1,26 +1,23 @@ -from panther_base_helpers import deep_get - - def rule(event): - if deep_get(event, "id", "applicationName") != "rules": + if event.deep_get("id", "applicationName") != "rules": return False - if not deep_get(event, "parameters", "triggered_actions"): + if not event.deep_get("parameters", "triggered_actions"): return False return True def title(event): - rule_severity = deep_get(event, "parameters", "severity") - if deep_get(event, "parameters", "rule_name"): + rule_severity = event.deep_get("parameters", "severity") + if event.deep_get("parameters", "rule_name"): return ( "GSuite " + rule_severity + " Severity Rule Triggered: " - + deep_get(event, "parameters", "rule_name") + + event.deep_get("parameters", "rule_name") ) return "GSuite " + rule_severity + " Severity Rule Triggered" def severity(event): - return deep_get(event, "parameters", "severity", default="INFO") + return event.deep_get("parameters", "severity", default="INFO") diff --git a/rules/gsuite_activityevent_rules/gsuite_suspicious_logins.py b/rules/gsuite_activityevent_rules/gsuite_suspicious_logins.py index 8cd73941b..51d71961b 100644 --- a/rules/gsuite_activityevent_rules/gsuite_suspicious_logins.py +++ b/rules/gsuite_activityevent_rules/gsuite_suspicious_logins.py @@ -1,5 +1,3 @@ -from panther_base_helpers import deep_get - SUSPICIOUS_LOGIN_TYPES = { "suspicious_login", "suspicious_login_less_secure_app", @@ -8,14 +6,14 @@ def rule(event): - if deep_get(event, "id", "applicationName") != "login": + if event.deep_get("id", "applicationName") != "login": return False return bool(event.get("name") in SUSPICIOUS_LOGIN_TYPES) def title(event): - user = deep_get(event, "parameters", "affected_email_address") + user = event.deep_get("parameters", "affected_email_address") if not user: user = "" return f"A suspicious login was reported for user [{user}]" diff --git a/rules/gsuite_activityevent_rules/gsuite_two_step_verification.py b/rules/gsuite_activityevent_rules/gsuite_two_step_verification.py index cece50b43..700508e22 100644 --- a/rules/gsuite_activityevent_rules/gsuite_two_step_verification.py +++ b/rules/gsuite_activityevent_rules/gsuite_two_step_verification.py @@ -1,8 +1,5 @@ -from panther_base_helpers import deep_get - - def rule(event): - if deep_get(event, "id", "applicationName") != "user_accounts": + if event.deep_get("id", "applicationName") != "user_accounts": return False if event.get("type") == "2sv_change" and event.get("name") == "2sv_disable": @@ -14,5 +11,5 @@ def rule(event): def title(event): return ( f"Two step verification was disabled for user" - f" [{deep_get(event, 'actor', 'email', default='')}]" + f" [{event.deep_get('actor', 'email', default='')}]" ) diff --git a/rules/gsuite_activityevent_rules/gsuite_user_suspended.py b/rules/gsuite_activityevent_rules/gsuite_user_suspended.py index c8a37a0a0..5fffb635d 100644 --- a/rules/gsuite_activityevent_rules/gsuite_user_suspended.py +++ b/rules/gsuite_activityevent_rules/gsuite_user_suspended.py @@ -1,5 +1,3 @@ -from panther_base_helpers import deep_get - USER_SUSPENDED_EVENTS = { "account_disabled_generic", "account_disabled_spamming_through_relay", @@ -9,14 +7,14 @@ def rule(event): - if deep_get(event, "id", "applicationName") != "login": + if event.deep_get("id", "applicationName") != "login": return False return bool(event.get("name") in USER_SUSPENDED_EVENTS) def title(event): - user = deep_get(event, "parameters", "affected_email_address") + user = event.deep_get("parameters", "affected_email_address") if not user: user = "" return f"User [{user}]'s account was disabled" diff --git a/rules/gsuite_activityevent_rules/gsuite_workspace_calendar_external_sharing.py b/rules/gsuite_activityevent_rules/gsuite_workspace_calendar_external_sharing.py index 2dcc60f13..1a9c7b97f 100644 --- a/rules/gsuite_activityevent_rules/gsuite_workspace_calendar_external_sharing.py +++ b/rules/gsuite_activityevent_rules/gsuite_workspace_calendar_external_sharing.py @@ -1,15 +1,12 @@ -from panther_base_helpers import deep_get - - def rule(event): if not all( [ (event.get("name", "") == "CHANGE_CALENDAR_SETTING"), - (deep_get(event, "parameters", "SETTING_NAME", default="") == "SHARING_OUTSIDE_DOMAIN"), + (event.deep_get("parameters", "SETTING_NAME", default="") == "SHARING_OUTSIDE_DOMAIN"), ] ): return False - return deep_get(event, "parameters", "NEW_VALUE", default="") in [ + return event.deep_get("parameters", "NEW_VALUE", default="") in [ "READ_WRITE_ACCESS", "READ_ONLY_ACCESS", "MANAGE_ACCESS", @@ -19,7 +16,7 @@ def rule(event): def title(event): return ( f"GSuite workspace setting for default calendar sharing was changed by " - f"[{deep_get(event, 'actor', 'email', default='')}] " - f"from [{deep_get(event, 'parameters', 'OLD_VALUE', default='')}] " - f"to [{deep_get(event, 'parameters', 'NEW_VALUE', default='')}]" + f"[{event.deep_get('actor', 'email', default='')}] " + f"from [{event.deep_get('parameters', 'OLD_VALUE', default='')}] " + f"to [{event.deep_get('parameters', 'NEW_VALUE', default='')}]" ) diff --git a/rules/gsuite_activityevent_rules/gsuite_workspace_data_export_created.py b/rules/gsuite_activityevent_rules/gsuite_workspace_data_export_created.py index 46ddc5787..91c9b717d 100644 --- a/rules/gsuite_activityevent_rules/gsuite_workspace_data_export_created.py +++ b/rules/gsuite_activityevent_rules/gsuite_workspace_data_export_created.py @@ -1,6 +1,3 @@ -from panther_base_helpers import deep_get - - def rule(event): return event.get("name", "").startswith("CUSTOMER_TAKEOUT_") @@ -9,5 +6,5 @@ def title(event): return ( f"GSuite Workspace Data Export " f"[{event.get('name', '')}] " - f"performed by [{deep_get(event, 'actor', 'email', default='')}]" + f"performed by [{event.deep_get('actor', 'email', default='')}]" ) diff --git a/rules/gsuite_activityevent_rules/gsuite_workspace_gmail_default_routing_rule.py b/rules/gsuite_activityevent_rules/gsuite_workspace_gmail_default_routing_rule.py index 93ca7562d..e6d6c0d94 100644 --- a/rules/gsuite_activityevent_rules/gsuite_workspace_gmail_default_routing_rule.py +++ b/rules/gsuite_activityevent_rules/gsuite_workspace_gmail_default_routing_rule.py @@ -1,12 +1,9 @@ -from panther_base_helpers import deep_get - - def rule(event): if all( [ (event.get("type", "") == "EMAIL_SETTINGS"), (event.get("name", "").endswith("_GMAIL_SETTING")), - (deep_get(event, "parameters", "SETTING_NAME", default="") == "MESSAGE_SECURITY_RULE"), + (event.deep_get("parameters", "SETTING_NAME", default="") == "MESSAGE_SECURITY_RULE"), ] ): return True @@ -21,5 +18,5 @@ def title(event): return ( f"GSuite Gmail Default Routing Rule Was " f"[{change_type}] " - f"by [{deep_get(event, 'actor', 'email', default='')}]" + f"by [{event.deep_get('actor', 'email', default='')}]" ) diff --git a/rules/gsuite_activityevent_rules/gsuite_workspace_gmail_enhanced_predelivery_scanning.py b/rules/gsuite_activityevent_rules/gsuite_workspace_gmail_enhanced_predelivery_scanning.py index ade01e8d1..278561275 100644 --- a/rules/gsuite_activityevent_rules/gsuite_workspace_gmail_enhanced_predelivery_scanning.py +++ b/rules/gsuite_activityevent_rules/gsuite_workspace_gmail_enhanced_predelivery_scanning.py @@ -1,18 +1,15 @@ -from panther_base_helpers import deep_get - - def rule(event): # the shape of the items in parameters can change a bit ( like NEW_VALUE can be an array ) # when the applicationName is something other than admin - if deep_get(event, "id", "applicationName", default="").lower() != "admin": + if event.deep_get("id", "applicationName", default="").lower() != "admin": return False if all( [ (event.get("name", "") == "CHANGE_APPLICATION_SETTING"), - (deep_get(event, "parameters", "APPLICATION_NAME", default="").lower() == "gmail"), - (deep_get(event, "parameters", "NEW_VALUE", default="").lower() == "true"), + (event.deep_get("parameters", "APPLICATION_NAME", default="").lower() == "gmail"), + (event.deep_get("parameters", "NEW_VALUE", default="").lower() == "true"), ( - deep_get(event, "parameters", "SETTING_NAME", default="") + event.deep_get("parameters", "SETTING_NAME", default="") == "DelayedDeliverySettingsProto disable_delayed_delivery_for_suspicious_email" ), ] @@ -24,6 +21,6 @@ def rule(event): def title(event): return ( f"GSuite Gmail Enhanced Pre-Delivery Scanning was disabled " - f"for [{deep_get(event, 'parameters', 'ORG_UNIT_NAME', default='')}] " - f"by [{deep_get(event, 'actor', 'email', default='')}]" + f"for [{event.deep_get('parameters', 'ORG_UNIT_NAME', default='')}] " + f"by [{event.deep_get('actor', 'email', default='')}]" ) diff --git a/rules/gsuite_activityevent_rules/gsuite_workspace_gmail_security_sandbox_disabled.py b/rules/gsuite_activityevent_rules/gsuite_workspace_gmail_security_sandbox_disabled.py index 75f57ed24..7f881be1e 100644 --- a/rules/gsuite_activityevent_rules/gsuite_workspace_gmail_security_sandbox_disabled.py +++ b/rules/gsuite_activityevent_rules/gsuite_workspace_gmail_security_sandbox_disabled.py @@ -1,16 +1,13 @@ -from panther_base_helpers import deep_get - - def rule(event): - if deep_get(event, "id", "applicationName", default="").lower() != "admin": + if event.deep_get("id", "applicationName", default="").lower() != "admin": return False if all( [ (event.get("name", "") == "CHANGE_APPLICATION_SETTING"), - (deep_get(event, "parameters", "APPLICATION_NAME", default="").lower() == "gmail"), - (deep_get(event, "parameters", "NEW_VALUE", default="").lower() == "false"), + (event.deep_get("parameters", "APPLICATION_NAME", default="").lower() == "gmail"), + (event.deep_get("parameters", "NEW_VALUE", default="").lower() == "false"), ( - deep_get(event, "parameters", "SETTING_NAME", default="") + event.deep_get("parameters", "SETTING_NAME", default="") == "AttachmentDeepScanningSettingsProto deep_scanning_enabled" ), ] @@ -22,6 +19,6 @@ def rule(event): def title(event): return ( f"GSuite Gmail Security Sandbox was disabled " - f"for [{deep_get(event, 'parameters', 'ORG_UNIT_NAME', default='')}] " - f"by [{deep_get(event, 'actor', 'email', default='')}]" + f"for [{event.deep_get('parameters', 'ORG_UNIT_NAME', default='')}] " + f"by [{event.deep_get('actor', 'email', default='')}]" ) diff --git a/rules/gsuite_activityevent_rules/gsuite_workspace_password_enforce_strong_disabled.py b/rules/gsuite_activityevent_rules/gsuite_workspace_password_enforce_strong_disabled.py index 1dbda3981..43c9e59e2 100644 --- a/rules/gsuite_activityevent_rules/gsuite_workspace_password_enforce_strong_disabled.py +++ b/rules/gsuite_activityevent_rules/gsuite_workspace_password_enforce_strong_disabled.py @@ -1,16 +1,13 @@ -from panther_base_helpers import deep_get - - def rule(event): - if deep_get(event, "id", "applicationName", default="").lower() != "admin": + if event.deep_get("id", "applicationName", default="").lower() != "admin": return False if all( [ (event.get("name", "") == "CHANGE_APPLICATION_SETTING"), (event.get("type", "") == "APPLICATION_SETTINGS"), - (deep_get(event, "parameters", "NEW_VALUE", default="").lower() == "off"), + (event.deep_get("parameters", "NEW_VALUE", default="").lower() == "off"), ( - deep_get(event, "parameters", "SETTING_NAME", default="") + event.deep_get("parameters", "SETTING_NAME", default="") == "Password Management - Enforce strong password" ), ] @@ -22,5 +19,5 @@ def rule(event): def title(event): return ( f"GSuite Workspace Strong Password Enforcement Has Been Disabled " - f"By [{deep_get(event, 'actor', 'email', default='')}]" + f"By [{event.deep_get('actor', 'email', default='')}]" ) diff --git a/rules/gsuite_activityevent_rules/gsuite_workspace_password_reuse_enabled.py b/rules/gsuite_activityevent_rules/gsuite_workspace_password_reuse_enabled.py index c0afefb91..f70f13650 100644 --- a/rules/gsuite_activityevent_rules/gsuite_workspace_password_reuse_enabled.py +++ b/rules/gsuite_activityevent_rules/gsuite_workspace_password_reuse_enabled.py @@ -1,16 +1,13 @@ -from panther_base_helpers import deep_get - - def rule(event): - if deep_get(event, "id", "applicationName", default="").lower() != "admin": + if event.deep_get("id", "applicationName", default="").lower() != "admin": return False if all( [ (event.get("name", "") == "CHANGE_APPLICATION_SETTING"), (event.get("type", "") == "APPLICATION_SETTINGS"), - (deep_get(event, "parameters", "NEW_VALUE", default="").lower() == "true"), + (event.deep_get("parameters", "NEW_VALUE", default="").lower() == "true"), ( - deep_get(event, "parameters", "SETTING_NAME", default="") + event.deep_get("parameters", "SETTING_NAME", default="") == "Password Management - Enable password reuse" ), ] @@ -22,5 +19,5 @@ def rule(event): def title(event): return ( f"GSuite Workspace Password Reuse Has Been Enabled " - f"By [{deep_get(event, 'actor', 'email', default='')}]" + f"By [{event.deep_get('actor', 'email', default='')}]" ) diff --git a/rules/gsuite_activityevent_rules/gsuite_workspace_trusted_domains_allowlist.py b/rules/gsuite_activityevent_rules/gsuite_workspace_trusted_domains_allowlist.py index 46f8f592c..27b5d573d 100644 --- a/rules/gsuite_activityevent_rules/gsuite_workspace_trusted_domains_allowlist.py +++ b/rules/gsuite_activityevent_rules/gsuite_workspace_trusted_domains_allowlist.py @@ -1,6 +1,3 @@ -from panther_base_helpers import deep_get - - def rule(event): return event.get("type") == "DOMAIN_SETTINGS" and event.get("name", "").endswith( "_TRUSTED_DOMAINS" @@ -11,6 +8,6 @@ def title(event): return ( f"GSuite Workspace Trusted Domains Modified " f"[{event.get('name', '')}] " - f"with [{deep_get(event, 'parameters', 'DOMAIN_NAME', default='')}] " - f"performed by [{deep_get(event, 'actor', 'email', default='')}]" + f"with [{event.deep_get('parameters', 'DOMAIN_NAME', default='')}] " + f"performed by [{event.deep_get('actor', 'email', default='')}]" ) diff --git a/rules/gsuite_reports_rules/gsuite_drive_external_share.py b/rules/gsuite_reports_rules/gsuite_drive_external_share.py index 03992ecdd..0004c8ebb 100644 --- a/rules/gsuite_reports_rules/gsuite_drive_external_share.py +++ b/rules/gsuite_reports_rules/gsuite_drive_external_share.py @@ -1,6 +1,6 @@ import datetime -from panther_base_helpers import deep_get, pattern_match, pattern_match_list +from panther_base_helpers import pattern_match, pattern_match_list COMPANY_DOMAIN = "your-company-name.com" EXCEPTION_PATTERNS = { @@ -63,9 +63,9 @@ def _check_acl_change_event(actor_email, acl_change_event): def rule(event): - application_name = deep_get(event, "id", "applicationName") + application_name = event.deep_get("id", "applicationName") events = event.get("events") - actor_email = deep_get(event, "actor", "email", default="EMAIL_UNKNOWN") + actor_email = event.deep_get("actor", "email", default="EMAIL_UNKNOWN") if application_name == "drive" and events and "acl_change" in set(e["type"] for e in events): # If any of the events in this record are a dangerous file share, alert: @@ -77,7 +77,7 @@ def rule(event): def title(event): events = event.get("events", []) - actor_email = deep_get(event, "actor", "email", default="EMAIL_UNKNOWN") + actor_email = event.deep_get("actor", "email", default="EMAIL_UNKNOWN") matching_events = [ _check_acl_change_event(actor_email, acl_change_event) for acl_change_event in events diff --git a/rules/gsuite_reports_rules/gsuite_drive_overly_visible.py b/rules/gsuite_reports_rules/gsuite_drive_overly_visible.py index 4c26b69d1..b45cb0ac3 100644 --- a/rules/gsuite_reports_rules/gsuite_drive_overly_visible.py +++ b/rules/gsuite_reports_rules/gsuite_drive_overly_visible.py @@ -1,4 +1,3 @@ -from panther_base_helpers import deep_get from panther_base_helpers import gsuite_details_lookup as details_lookup from panther_base_helpers import gsuite_parameter_lookup as param_lookup @@ -16,7 +15,7 @@ def rule(event): - if deep_get(event, "id", "applicationName") != "drive": + if event.deep_get("id", "applicationName") != "drive": return False details = details_lookup("access", RESOURCE_CHANGE_EVENTS, event) @@ -27,9 +26,9 @@ def rule(event): def dedup(event): - user = deep_get(event, "actor", "email") + user = event.deep_get("actor", "email") if user is None: - user = deep_get(event, "actor", "profileId", default="") + user = event.deep_get("actor", "profileId", default="") return user @@ -37,9 +36,9 @@ def title(event): details = details_lookup("access", RESOURCE_CHANGE_EVENTS, event) doc_title = param_lookup(details.get("parameters", {}), "doc_title") share_settings = param_lookup(details.get("parameters", {}), "visibility") - user = deep_get(event, "actor", "email") + user = event.deep_get("actor", "email") if user is None: - user = deep_get(event, "actor", "profileId", default="") + user = event.deep_get("actor", "profileId", default="") return ( f"User [{user}]" f" modified a document [{doc_title}] that has overly permissive share" diff --git a/rules/gsuite_reports_rules/gsuite_drive_visibility_change.py b/rules/gsuite_reports_rules/gsuite_drive_visibility_change.py index 1103fef58..75bf1902b 100644 --- a/rules/gsuite_reports_rules/gsuite_drive_visibility_change.py +++ b/rules/gsuite_reports_rules/gsuite_drive_visibility_change.py @@ -1,7 +1,6 @@ import json from unittest.mock import MagicMock -from panther_base_helpers import deep_get from panther_base_helpers import gsuite_parameter_lookup as param_lookup # Add any domain name(s) that you expect to share documents with in the ALLOWED_DOMAINS set @@ -67,7 +66,7 @@ def user_is_external(target_user): def rule(event): # pylint: disable=too-complex global ALLOWED_DOMAINS # pylint: disable=global-statement - if deep_get(event, "id", "applicationName") != "drive": + if event.deep_get("id", "applicationName") != "drive": return False # Events that have the types in INHERITANCE_EVENTS are @@ -75,7 +74,7 @@ def rule(event): # a change in the parent folder's permission. We ignore # these events to prevent every folder change from # generating multiple alerts. - if deep_get(event, "events", "name") in INHERITANCE_EVENTS: + if event.deep_get("events", "name") in INHERITANCE_EVENTS: return False log = event.get("p_row_id") @@ -167,7 +166,7 @@ def alert_context(event): def dedup(event): - return deep_get(event, "actor", "email", default="") + return event.deep_get("actor", "email", default="") def title(event): @@ -195,7 +194,7 @@ def title(event): # alert_access_scope = ALERT_DETAILS[log]["ACCESS_SCOPE"][0].replace("can_", "") return ( - f"User [{deep_get(event, 'actor', 'email', default='')}] made documents " + f"User [{event.deep_get('actor', 'email', default='')}] made documents " f"externally visible" ) diff --git a/rules/mongodb_rules/mongodb_2fa_disabled.py b/rules/mongodb_rules/mongodb_2fa_disabled.py index 0f2240653..e81da94a4 100644 --- a/rules/mongodb_rules/mongodb_2fa_disabled.py +++ b/rules/mongodb_rules/mongodb_2fa_disabled.py @@ -2,11 +2,11 @@ def rule(event): - return event.deep_get("eventTypeName", default="") == "ORG_TWO_FACTOR_AUTH_OPTIONAL" + return event.get("eventTypeName", "") == "ORG_TWO_FACTOR_AUTH_OPTIONAL" def title(event): - user = event.deep_get("username", default="") + user = event.get("username", "") return f"MongoDB Atlas: [{user}] has disabled 2FA" diff --git a/rules/mongodb_rules/mongodb_access_allowed_from_anywhere.py b/rules/mongodb_rules/mongodb_access_allowed_from_anywhere.py index 8e7bbe53c..aed2699ea 100644 --- a/rules/mongodb_rules/mongodb_access_allowed_from_anywhere.py +++ b/rules/mongodb_rules/mongodb_access_allowed_from_anywhere.py @@ -3,20 +3,20 @@ def rule(event): if ( - event.deep_get("eventTypeName", default="") == "NETWORK_PERMISSION_ENTRY_ADDED" - and event.deep_get("whitelistEntry", default="") == "0.0.0.0/0" + event.get("eventTypeName", "") == "NETWORK_PERMISSION_ENTRY_ADDED" + and event.get("whitelistEntry", "") == "0.0.0.0/0" ): return True return False def title(event): - user = event.deep_get("username", default="") - group_id = event.deep_get("groupId", default="") + user = event.get("username", "") + group_id = event.get("groupId", "") return f"MongoDB: [{user}] has allowed access to group [{group_id}] from anywhere" def alert_context(event): context = mongodb_alert_context(event) - context["groupId"] = event.deep_get("groupId", default="") + context["groupId"] = event.get("groupId", "") return context diff --git a/rules/mongodb_rules/mongodb_alerting_disabled.py b/rules/mongodb_rules/mongodb_alerting_disabled.py index cf873eefd..7b74b323e 100644 --- a/rules/mongodb_rules/mongodb_alerting_disabled.py +++ b/rules/mongodb_rules/mongodb_alerting_disabled.py @@ -2,19 +2,19 @@ def rule(event): - return event.deep_get("eventTypeName", default="") in [ + return event.get("eventTypeName", "") in [ "ALERT_CONFIG_DISABLED_AUDIT", "ALERT_CONFIG_DELETED_AUDIT", ] def title(event): - user = event.deep_get("username", default="") - alert_id = event.deep_get("alertConfigId", default="") + user = event.get("username", "") + alert_id = event.get("alertConfigId", "") return f"MongoDB: [{user}] has disabled or deleted security alert [{alert_id}]" def alert_context(event): context = mongodb_alert_context(event) - context["alertConfigId"] = event.deep_get("alertConfigId", default="") + context["alertConfigId"] = event.get("alertConfigId", "") return context diff --git a/rules/mongodb_rules/mongodb_atlas_api_key_created.py b/rules/mongodb_rules/mongodb_atlas_api_key_created.py index 5a8d6ff33..981fc2a17 100644 --- a/rules/mongodb_rules/mongodb_atlas_api_key_created.py +++ b/rules/mongodb_rules/mongodb_atlas_api_key_created.py @@ -1,24 +1,23 @@ -from panther_base_helpers import deep_get, deep_walk from panther_mongodb_helpers import mongodb_alert_context def rule(event): - return deep_get(event, "eventTypeName", default="") == "API_KEY_ACCESS_LIST_ENTRY_ADDED" + return event.get("eventTypeName", "") == "API_KEY_ACCESS_LIST_ENTRY_ADDED" def title(event): - user = deep_get(event, "username", default="") - public_key = deep_get(event, "targetPublicKey", default="") + user = event.get("username", "") + public_key = event.get("targetPublicKey", "") return f"MongoDB Atlas: [{user}] updated the allowed access list for API Key [{public_key}]" def alert_context(event): context = mongodb_alert_context(event) - links = deep_walk(event, "links", "href", return_val="first", default="") + links = event.deep_walk("links", "href", return_val="first", default="") extra_context = { "links": links, - "event_type_name": deep_get(event, "eventTypeName", default=""), - "target_public_key": deep_get(event, "targetPublicKey", default=""), + "event_type_name": event.get("eventTypeName", ""), + "target_public_key": event.get("targetPublicKey", ""), } context.update(extra_context) diff --git a/rules/mongodb_rules/mongodb_external_user_invited.py b/rules/mongodb_rules/mongodb_external_user_invited.py index f4c9de06c..de43af6df 100644 --- a/rules/mongodb_rules/mongodb_external_user_invited.py +++ b/rules/mongodb_rules/mongodb_external_user_invited.py @@ -1,7 +1,6 @@ import json from unittest.mock import MagicMock -from panther_base_helpers import deep_get from panther_mongodb_helpers import mongodb_alert_context # Set domains allowed to join the organization ie. company.com @@ -12,8 +11,8 @@ def rule(event): global ALLOWED_DOMAINS # pylint: disable=global-statement if isinstance(ALLOWED_DOMAINS, MagicMock): ALLOWED_DOMAINS = json.loads(ALLOWED_DOMAINS()) # pylint: disable=not-callable - if deep_get(event, "eventTypeName", default="") == "INVITED_TO_ORG": - target_user = deep_get(event, "targetUsername", default="") + if event.get("eventTypeName", "") == "INVITED_TO_ORG": + target_user = event.get("targetUsername", "") target_domain = target_user.split("@")[-1] return target_domain not in ALLOWED_DOMAINS return False diff --git a/rules/mongodb_rules/mongodb_external_user_invited_no_config.py b/rules/mongodb_rules/mongodb_external_user_invited_no_config.py index c84ebd315..345295695 100644 --- a/rules/mongodb_rules/mongodb_external_user_invited_no_config.py +++ b/rules/mongodb_rules/mongodb_external_user_invited_no_config.py @@ -2,11 +2,11 @@ def rule(event): - if event.deep_get("eventTypeName", default="") != "INVITED_TO_ORG": + if event.get("eventTypeName", "") != "INVITED_TO_ORG": return False - user_who_sent_an_invitation = event.deep_get("username", default="") - user_who_was_invited = event.deep_get("targetUsername", default="") + user_who_sent_an_invitation = event.get("username", "") + user_who_was_invited = event.get("targetUsername", "") domain = user_who_sent_an_invitation.split("@")[-1] email_domains_are_different = not user_who_was_invited.endswith(domain) diff --git a/rules/mongodb_rules/mongodb_identity_provider_activity.py b/rules/mongodb_rules/mongodb_identity_provider_activity.py index 62e39c16d..be48207c6 100644 --- a/rules/mongodb_rules/mongodb_identity_provider_activity.py +++ b/rules/mongodb_rules/mongodb_identity_provider_activity.py @@ -16,7 +16,7 @@ def rule(event): "OIDC_IDENTITY_PROVIDER_ENABLED", "OIDC_IDENTITY_PROVIDER_DISABLED", } - return event.deep_get("eventTypeName") in important_event_types + return event.get("eventTypeName") in important_event_types def title(event): diff --git a/rules/mongodb_rules/mongodb_logging_toggled.py b/rules/mongodb_rules/mongodb_logging_toggled.py index 18d6c1ba6..ebd390214 100644 --- a/rules/mongodb_rules/mongodb_logging_toggled.py +++ b/rules/mongodb_rules/mongodb_logging_toggled.py @@ -2,11 +2,11 @@ def rule(event): - return event.deep_get("eventTypeName", default="") == "AUDIT_LOG_CONFIGURATION_UPDATED" + return event.get("eventTypeName", "") == "AUDIT_LOG_CONFIGURATION_UPDATED" def title(event): - user = event.deep_get("username", default="") + user = event.get("username", "") return f"MongoDB: [{user}] has changed logging configuration." diff --git a/rules/mongodb_rules/mongodb_org_membership_restriction_disabled.py b/rules/mongodb_rules/mongodb_org_membership_restriction_disabled.py index 987fc2128..4434bfe27 100644 --- a/rules/mongodb_rules/mongodb_org_membership_restriction_disabled.py +++ b/rules/mongodb_rules/mongodb_org_membership_restriction_disabled.py @@ -2,11 +2,11 @@ def rule(event): - return event.deep_get("eventTypeName", default="") == "ORG_PUBLIC_API_ACCESS_LIST_NOT_REQUIRED" + return event.get("eventTypeName", "") == "ORG_PUBLIC_API_ACCESS_LIST_NOT_REQUIRED" def title(event): - user = event.deep_get("username", default="") + user = event.get("username", "") return f"MongoDB: [{user}] has disabled IP access list for the Atlas Administration API" diff --git a/rules/mongodb_rules/mongodb_user_created_or_deleted.py b/rules/mongodb_rules/mongodb_user_created_or_deleted.py index ebe6e90c4..d3f476b14 100644 --- a/rules/mongodb_rules/mongodb_user_created_or_deleted.py +++ b/rules/mongodb_rules/mongodb_user_created_or_deleted.py @@ -2,7 +2,7 @@ def rule(event): - return event.deep_get("eventTypeName", default="") in ("JOINED_ORG", "REMOVED_FROM_ORG") + return event.get("eventTypeName", "") in ("JOINED_ORG", "REMOVED_FROM_ORG") def title(event): diff --git a/rules/mongodb_rules/mongodb_user_roles_changed.py b/rules/mongodb_rules/mongodb_user_roles_changed.py index 4add72f50..226b1271c 100644 --- a/rules/mongodb_rules/mongodb_user_roles_changed.py +++ b/rules/mongodb_rules/mongodb_user_roles_changed.py @@ -2,7 +2,7 @@ def rule(event): - return event.deep_get("eventTypeName") == "USER_ROLES_CHANGED_AUDIT" + return event.get("eventTypeName") == "USER_ROLES_CHANGED_AUDIT" def title(event): diff --git a/rules/netskope_rules/netskope_unauthorized_api_calls.py b/rules/netskope_rules/netskope_unauthorized_api_calls.py index 9f3e96e12..f803996e6 100644 --- a/rules/netskope_rules/netskope_unauthorized_api_calls.py +++ b/rules/netskope_rules/netskope_unauthorized_api_calls.py @@ -1,8 +1,5 @@ -from panther_base_helpers import deep_walk - - def rule(event): - data_values = deep_walk(event, "supporting_data", "data_values") + data_values = event.deep_walk("supporting_data", "data_values") if data_values and data_values[0] == 403: return True return False diff --git a/rules/notion_rules/notion_workspace_settings_enforce_saml_sso_config_updated.py b/rules/notion_rules/notion_workspace_settings_enforce_saml_sso_config_updated.py index 786733df0..da87502c3 100644 --- a/rules/notion_rules/notion_workspace_settings_enforce_saml_sso_config_updated.py +++ b/rules/notion_rules/notion_workspace_settings_enforce_saml_sso_config_updated.py @@ -1,5 +1,4 @@ from global_filter_notion import filter_include_event -from panther_base_helpers import deep_get from panther_notion_helpers import notion_alert_context @@ -15,8 +14,7 @@ def rule(event): def title(event): user = event.deep_get("event", "actor", "person", "email", default="") workspace_id = event.deep_get("event", "workspace_id", default="") - state = deep_get( - event, + state = event.deep_get( "event", "workspace.settings.enforce_saml_sso_config_updated", "state", @@ -36,8 +34,7 @@ def title(event): def severity(event): - state = deep_get( - event, + state = event.deep_get( "event", "workspace.settings.enforce_saml_sso_config_updated", "state", diff --git a/rules/notion_rules/notion_workspace_settings_public_homepage_added.py b/rules/notion_rules/notion_workspace_settings_public_homepage_added.py index 5799e041a..91c4f56e9 100644 --- a/rules/notion_rules/notion_workspace_settings_public_homepage_added.py +++ b/rules/notion_rules/notion_workspace_settings_public_homepage_added.py @@ -1,5 +1,4 @@ from global_filter_notion import filter_include_event -from panther_base_helpers import deep_get from panther_notion_helpers import notion_alert_context @@ -14,8 +13,7 @@ def rule(event): def title(event): actor = event.deep_get("event", "actor", "person", "email", default="") wkspc_id = event.deep_get("event", "workspace_id", default="") - db_id = deep_get( - event, + db_id = event.deep_get( "event", "workspace.settings.public_homepage_added", "new_public_page", @@ -28,8 +26,7 @@ def title(event): def alert_context(event): context = notion_alert_context(event) workspace_id = event.deep_get("event", "workspace_id", default="") - db_id = deep_get( - event, + db_id = event.deep_get( "event", "workspace.settings.public_homepage_added", "new_public_page", diff --git a/rules/okta_rules/okta_admin_role_assigned.py b/rules/okta_rules/okta_admin_role_assigned.py index a4ba3a087..4d21017e0 100644 --- a/rules/okta_rules/okta_admin_role_assigned.py +++ b/rules/okta_rules/okta_admin_role_assigned.py @@ -1,6 +1,6 @@ import re -from panther_base_helpers import deep_get, okta_alert_context +from panther_base_helpers import okta_alert_context ADMIN_PATTERN = re.compile(r"[aA]dministrator") @@ -8,30 +8,30 @@ def rule(event): return ( event.get("eventType", None) == "user.account.privilege.grant" - and deep_get(event, "outcome", "result") == "SUCCESS" + and event.deep_get("outcome", "result") == "SUCCESS" and bool( ADMIN_PATTERN.search( - deep_get(event, "debugContext", "debugData", "privilegeGranted", default="") + event.deep_get("debugContext", "debugData", "privilegeGranted", default="") ) ) ) def dedup(event): - return deep_get(event, "debugContext", "debugData", "requestId", default="") + return event.deep_get("debugContext", "debugData", "requestId", default="") def title(event): target = event.get("target", [{}]) display_name = target[0].get("displayName", "MISSING DISPLAY NAME") if target else "" alternate_id = target[0].get("alternateId", "MISSING ALTERNATE ID") if target else "" - privilege = deep_get( - event, "debugContext", "debugData", "privilegeGranted", default="" + privilege = event.deep_get( + "debugContext", "debugData", "privilegeGranted", default="" ) return ( - f"{deep_get(event, 'actor', 'displayName')} " - f"<{deep_get(event, 'actor', 'alternateId')}> granted " + f"{event.deep_get('actor', 'displayName')} " + f"<{event.deep_get('actor', 'alternateId')}> granted " f"[{privilege}] privileges to {display_name} <{alternate_id}>" ) @@ -41,8 +41,8 @@ def alert_context(event): def severity(event): - if "Super administrator" in deep_get( - event, "debugContext", "debugData", "privilegeGranted", default="" + if "Super administrator" in event.deep_get( + "debugContext", "debugData", "privilegeGranted", default="" ): return "HIGH" return "INFO" diff --git a/rules/okta_rules/okta_anonymizing_vpn_login.py b/rules/okta_rules/okta_anonymizing_vpn_login.py index 51094fd8d..48cc62ca2 100644 --- a/rules/okta_rules/okta_anonymizing_vpn_login.py +++ b/rules/okta_rules/okta_anonymizing_vpn_login.py @@ -1,18 +1,18 @@ -from panther_base_helpers import deep_get, okta_alert_context +from panther_base_helpers import okta_alert_context def rule(event): - return event.get("eventType") == "user.session.start" and deep_get( - event, "securityContext", "isProxy", default=False + return event.get("eventType") == "user.session.start" and event.deep_get( + "securityContext", "isProxy", default=False ) def title(event): return ( - f"{deep_get(event, 'actor', 'displayName', default='')} " - f"<{deep_get(event, 'actor', 'alternateId', default='alternateId-not-found')}> " + f"{event.deep_get('actor', 'displayName', default='')} " + f"<{event.deep_get('actor', 'alternateId', default='alternateId-not-found')}> " f"attempted to sign-in from anonymizing VPN with domain " - f"[{deep_get(event, 'securityContext', 'domain', default='')}]" + f"[{event.deep_get('securityContext', 'domain', default='')}]" ) diff --git a/rules/okta_rules/okta_api_key_created.py b/rules/okta_rules/okta_api_key_created.py index 7f997f037..a4532ed01 100644 --- a/rules/okta_rules/okta_api_key_created.py +++ b/rules/okta_rules/okta_api_key_created.py @@ -1,10 +1,10 @@ -from panther_base_helpers import deep_get, okta_alert_context +from panther_base_helpers import okta_alert_context def rule(event): return ( event.get("eventType", None) == "system.api_token.create" - and deep_get(event, "outcome", "result") == "SUCCESS" + and event.deep_get("outcome", "result") == "SUCCESS" ) @@ -13,7 +13,7 @@ def title(event): key_name = target[0].get("displayName", "MISSING DISPLAY NAME") if target else "MISSING TARGET" return ( - f"{deep_get(event, 'actor', 'displayName')} <{deep_get(event, 'actor', 'alternateId')}>" + f"{event.deep_get('actor', 'displayName')} <{event.deep_get('actor', 'alternateId')}>" f"created a new API key - <{key_name}>" ) diff --git a/rules/okta_rules/okta_api_key_revoked.py b/rules/okta_rules/okta_api_key_revoked.py index 9a04cd35b..a24d47ef0 100644 --- a/rules/okta_rules/okta_api_key_revoked.py +++ b/rules/okta_rules/okta_api_key_revoked.py @@ -1,10 +1,10 @@ -from panther_base_helpers import deep_get, okta_alert_context +from panther_base_helpers import okta_alert_context def rule(event): return ( event.get("eventType", None) == "system.api_token.revoke" - and deep_get(event, "outcome", "result") == "SUCCESS" + and event.deep_get("outcome", "result") == "SUCCESS" ) @@ -13,7 +13,7 @@ def title(event): key_name = target[0].get("displayName", "MISSING DISPLAY NAME") if target else "MISSING TARGET" return ( - f"{deep_get(event, 'actor', 'displayName')} <{deep_get(event, 'actor', 'alternateId')}>" + f"{event.deep_get('actor', 'displayName')} <{event.deep_get('actor', 'alternateId')}>" f"revoked API key - <{key_name}>" ) diff --git a/rules/okta_rules/okta_app_unauthorized_access_attempt.py b/rules/okta_rules/okta_app_unauthorized_access_attempt.py index f47c0595a..57c8d9c04 100644 --- a/rules/okta_rules/okta_app_unauthorized_access_attempt.py +++ b/rules/okta_rules/okta_app_unauthorized_access_attempt.py @@ -1,4 +1,4 @@ -from panther_base_helpers import deep_get, okta_alert_context +from panther_base_helpers import okta_alert_context def rule(event): @@ -7,7 +7,7 @@ def rule(event): def title(event): return ( - f"[{deep_get(event, 'actor', 'alternateId', default = '')}] " + f"[{event.deep_get('actor', 'alternateId', default = '')}] " f"attempted unauthorized access to " f"[{event.get('target', [{}])[0].get('alternateId','')}]" ) diff --git a/rules/okta_rules/okta_idp_create_modify.py b/rules/okta_rules/okta_idp_create_modify.py index d62f22aa0..93902cfdd 100644 --- a/rules/okta_rules/okta_idp_create_modify.py +++ b/rules/okta_rules/okta_idp_create_modify.py @@ -1,4 +1,4 @@ -from panther_base_helpers import deep_get, deep_walk, okta_alert_context +from panther_base_helpers import okta_alert_context def rule(event): @@ -7,12 +7,12 @@ def rule(event): def title(event): action = event.get("eventType").split(".")[-1] - target = deep_walk( - event, "target", "displayName", default="", return_val="first" + target = event.deep_walk( + "target", "displayName", default="", return_val="first" ) return ( - f"{deep_get(event, 'actor', 'displayName', default='')} " - f"<{deep_get(event, 'actor', 'alternateId', default='alternateId-not-found')}> " + f"{event.deep_get('actor', 'displayName', default='')} " + f"<{event.deep_get('actor', 'alternateId', default='alternateId-not-found')}> " f"{action}d Identity Provider [{target}]" ) diff --git a/rules/okta_rules/okta_idp_signin.py b/rules/okta_rules/okta_idp_signin.py index 7676b0c3b..d9de91002 100644 --- a/rules/okta_rules/okta_idp_signin.py +++ b/rules/okta_rules/okta_idp_signin.py @@ -1,4 +1,4 @@ -from panther_base_helpers import deep_get, deep_walk, okta_alert_context +from panther_base_helpers import okta_alert_context def rule(event): @@ -6,12 +6,12 @@ def rule(event): def title(event): - target = deep_walk( - event, "target", "displayName", default="displayName-not-found", return_val="first" + target = event.deep_walk( + "target", "displayName", default="displayName-not-found", return_val="first" ) return ( - f"{deep_get(event, 'actor', 'displayName', default='')} " - f"<{deep_get(event, 'actor', 'alternateId', default='alternateId-not-found')}> " + f"{event.deep_get('actor', 'displayName', default='')} " + f"<{event.deep_get('actor', 'alternateId', default='alternateId-not-found')}> " f"signed in via 3rd party Identity Provider to {target}" ) diff --git a/rules/okta_rules/okta_login_signal.py b/rules/okta_rules/okta_login_signal.py index e7e9ab343..5ac6a2e48 100644 --- a/rules/okta_rules/okta_login_signal.py +++ b/rules/okta_rules/okta_login_signal.py @@ -1,6 +1,6 @@ def rule(event): return ( - event.deep_get("eventType") == "user.session.start" + event.get("eventType") == "user.session.start" and event.deep_get("outcome", "result") == "SUCCESS" ) diff --git a/rules/okta_rules/okta_new_behavior_accessing_admin_console.py b/rules/okta_rules/okta_new_behavior_accessing_admin_console.py index e82004e86..40def84b8 100644 --- a/rules/okta_rules/okta_new_behavior_accessing_admin_console.py +++ b/rules/okta_rules/okta_new_behavior_accessing_admin_console.py @@ -1,24 +1,24 @@ -from panther_base_helpers import deep_get, deep_walk, okta_alert_context +from panther_base_helpers import okta_alert_context def rule(event): if event.get("eventtype") != "policy.evaluate_sign_on": return False - if "Okta Admin Console" not in deep_walk(event, "target", "displayName", default=""): + if "Okta Admin Console" not in event.deep_walk("target", "displayName", default=""): return False - behaviors = deep_get(event, "debugContext", "debugData", "behaviors") + behaviors = event.deep_get("debugContext", "debugData", "behaviors") if behaviors: return "New Device=POSITIVE" in behaviors and "New IP=POSITIVE" in behaviors return ( - deep_get( - event, "debugContext", "debugData", "logOnlySecurityData", "behaviors", "New Device" + event.deep_get( + "debugContext", "debugData", "logOnlySecurityData", "behaviors", "New Device" ) == "POSITIVE" - and deep_get( - event, "debugContext", "debugData", "logOnlySecurityData", "behaviors", "New IP" + and event.deep_get( + "debugContext", "debugData", "logOnlySecurityData", "behaviors", "New IP" ) == "POSITIVE" ) @@ -26,11 +26,11 @@ def rule(event): def title(event): return ( - f"{deep_get(event, 'actor', 'displayName', default='')} " - f"<{deep_get(event, 'actor', 'alternateId', default='alternateId-not-found')}> " + f"{event.deep_get('actor', 'displayName', default='')} " + f"<{event.deep_get('actor', 'alternateId', default='alternateId-not-found')}> " f"accessed Okta Admin Console using new behaviors: " - f"New IP: {deep_get(event, 'client', 'ipAddress', default='')} " - f"New Device: {deep_get(event, 'device', 'name', default='')}" + f"New IP: {event.deep_get('client', 'ipAddress', default='')} " + f"New Device: {event.deep_get('device', 'name', default='')}" ) diff --git a/rules/okta_rules/okta_org2org_creation_modification.py b/rules/okta_rules/okta_org2org_creation_modification.py index 41bf07a70..0605bad09 100644 --- a/rules/okta_rules/okta_org2org_creation_modification.py +++ b/rules/okta_rules/okta_org2org_creation_modification.py @@ -1,4 +1,4 @@ -from panther_base_helpers import deep_get, deep_walk, okta_alert_context +from panther_base_helpers import okta_alert_context APP_LIFECYCLE_EVENTS = ( "application.lifecycle.update", @@ -11,17 +11,17 @@ def rule(event): if event.get("eventType") not in APP_LIFECYCLE_EVENTS: return False - return "Org2Org" in deep_walk(event, "target", "displayName", default="", return_val="first") + return "Org2Org" in event.deep_walk("target", "displayName", default="", return_val="first") def title(event): action = event.get("eventType").split(".")[-1] - target = deep_walk( - event, "target", "alternateId", default="", return_val="first" + target = event.deep_walk( + "target", "alternateId", default="", return_val="first" ) return ( - f"{deep_get(event, 'actor', 'displayName', default='')} " - f"<{deep_get(event, 'actor', 'alternateId', default='alternateId-not-found')}> " + f"{event.deep_get('actor', 'displayName', default='')} " + f"<{event.deep_get('actor', 'alternateId', default='alternateId-not-found')}> " f"{action}d Org2Org app [{target}]" ) diff --git a/rules/okta_rules/okta_password_accessed.py b/rules/okta_rules/okta_password_accessed.py index cfcc315cc..e3b4ef1d1 100644 --- a/rules/okta_rules/okta_password_accessed.py +++ b/rules/okta_rules/okta_password_accessed.py @@ -1,4 +1,4 @@ -from panther_base_helpers import deep_get, get_val_from_list +from panther_base_helpers import get_val_from_list # pylint: disable=global-variable-undefined @@ -16,13 +16,13 @@ def rule(event): event.get("target", [{}]), "alternateId", "type", "AppInstance" ) - if deep_get(event, "actor", "alternateId") not in TARGET_USERS: + if event.deep_get("actor", "alternateId") not in TARGET_USERS: return True return False def dedup(event): - dedup_str = deep_get(event, "actor", "alternateId") + dedup_str = event.deep_get("actor", "alternateId") if TARGET_USERS: dedup_str += ":" + str(TARGET_USERS) @@ -33,7 +33,7 @@ def dedup(event): def title(event): return ( - f"A user {deep_get(event, 'actor', 'alternateId')} accessed another user's " + f"A user {event.deep_get('actor', 'alternateId')} accessed another user's " f"{TARGET_USERS} " f"{TARGET_APP_NAMES} password" ) diff --git a/rules/okta_rules/okta_password_extraction_via_scim.py b/rules/okta_rules/okta_password_extraction_via_scim.py index 2f5984c04..b15d77c02 100644 --- a/rules/okta_rules/okta_password_extraction_via_scim.py +++ b/rules/okta_rules/okta_password_extraction_via_scim.py @@ -1,21 +1,21 @@ -from panther_base_helpers import deep_get, deep_walk, okta_alert_context +from panther_base_helpers import okta_alert_context def rule(event): return event.get( "eventType" - ) == "application.lifecycle.update" and "Pushing user passwords" in deep_get( - event, "outcome", "reason", default="" + ) == "application.lifecycle.update" and "Pushing user passwords" in event.deep_get( + "outcome", "reason", default="" ) def title(event): - target = deep_walk( - event, "target", "alternateId", default="", return_val="first" + target = event.deep_walk( + "target", "alternateId", default="", return_val="first" ) return ( - f"{deep_get(event, 'actor', 'displayName', default='')} " - f"<{deep_get(event, 'actor', 'alternateId', default='alternateId-not-found')}> " + f"{event.deep_get('actor', 'displayName', default='')} " + f"<{event.deep_get('actor', 'alternateId', default='alternateId-not-found')}> " f"extracted cleartext user passwords via SCIM app [{target}]" ) diff --git a/rules/okta_rules/okta_phishing_attempt_blocked_by_fastpass.py b/rules/okta_rules/okta_phishing_attempt_blocked_by_fastpass.py index d9ebe3b67..57834e399 100644 --- a/rules/okta_rules/okta_phishing_attempt_blocked_by_fastpass.py +++ b/rules/okta_rules/okta_phishing_attempt_blocked_by_fastpass.py @@ -1,18 +1,18 @@ -from panther_base_helpers import deep_get, okta_alert_context +from panther_base_helpers import okta_alert_context def rule(event): return ( event.get("eventType") == "user.authentication.auth_via_mfa" - and deep_get(event, "outcome", "result") == "FAILURE" - and deep_get(event, "outcome", "reason") == "FastPass declined phishing attempt" + and event.deep_get("outcome", "result") == "FAILURE" + and event.deep_get("outcome", "reason") == "FastPass declined phishing attempt" ) def title(event): return ( - f"{deep_get(event, 'actor', 'displayName', default='')} " - f"<{deep_get(event, 'actor', 'alternateId', default='alternateId-not-found')}> " + f"{event.deep_get('actor', 'displayName', default='')} " + f"<{event.deep_get('actor', 'alternateId', default='alternateId-not-found')}> " f"FastPass declined phishing attempt" ) diff --git a/rules/okta_rules/okta_potentially_stolen_session.py b/rules/okta_rules/okta_potentially_stolen_session.py index 1e052b274..b2eb15d78 100644 --- a/rules/okta_rules/okta_potentially_stolen_session.py +++ b/rules/okta_rules/okta_potentially_stolen_session.py @@ -2,7 +2,7 @@ from datetime import timedelta from difflib import SequenceMatcher -from panther_base_helpers import deep_get, okta_alert_context +from panther_base_helpers import okta_alert_context from panther_detection_helpers.caching import get_string_set, put_string_set FUZZ_RATIO_MIN = 0.95 @@ -17,15 +17,15 @@ def rule(event): # ensure previous session info is avaialable in the alert_context for investigation global PREVIOUS_SESSION - session_id = deep_get(event, "authenticationContext", "externalSessionId", default="unknown") - dt_hash = deep_get(event, "debugContext", "debugData", "dtHash", default="unknown") + session_id = event.deep_get("authenticationContext", "externalSessionId", default="unknown") + dt_hash = event.deep_get("debugContext", "debugData", "dtHash", default="unknown") # Some events by Okta admins may appear to have changed IPs # and user agents due to internal Okta behavior: # https://support.okta.com/help/s/article/okta-integrations-showing-as-rawuseragent-with-okta-ips # As such, we ignore certain client ids known to originate from Okta: # https://developer.okta.com/docs/api/openapi/okta-myaccount/myaccount/tag/OktaApplications/ - if deep_get(event, "client", "id") in [ + if event.deep_get("client", "id") in [ "okta.b58d5b75-07d4-5f25-bf59-368a1261a405" # Admin Console ]: return False @@ -50,18 +50,18 @@ def rule(event): put_string_set( key, [ - str(deep_get(event, "securityContext", "asNumber")), - deep_get(event, "client", "ipAddress"), + str(event.deep_get("securityContext", "asNumber")), + event.deep_get("client", "ipAddress"), # clearly label the user agent string so we can find it during the comparison - "user_agent:" + deep_get(event, "client", "userAgent", "rawUserAgent"), - deep_get(event, "client", "userAgent", "browser"), - deep_get(event, "client", "userAgent", "os"), + "user_agent:" + event.deep_get("client", "userAgent", "rawUserAgent"), + event.deep_get("client", "userAgent", "browser"), + event.deep_get("client", "userAgent", "os"), event.get("p_event_time"), "sign_on_mode:" - + deep_get(event, "debugContext", "debugData", "signOnMode", default="unknown"), + + event.deep_get("debugContext", "debugData", "signOnMode", default="unknown"), "threat_suspected:" - + deep_get( - event, "debugContext", "debugData", "threat_suspected", default="unknown" + + event.deep_get( + "debugContext", "debugData", "threat_suspected", default="unknown" ), ], epoch_seconds=event.event_time_epoch() + SESSION_TIMEOUT, @@ -79,13 +79,13 @@ def rule(event): diff_ratio = SequenceMatcher( None, - deep_get(event, "client", "userAgent", "rawUserAgent", default="ua_not_found"), + event.deep_get("client", "userAgent", "rawUserAgent", default="ua_not_found"), prev_ua, ).ratio() # is this session being used from a new IP and a different browser if ( - str(deep_get(event, "client", "ipAddress", default="ip_not_found")) + str(event.deep_get("client", "ipAddress", default="ip_not_found")) not in PREVIOUS_SESSION and diff_ratio < FUZZ_RATIO_MIN ): @@ -99,7 +99,7 @@ def rule(event): def title(event): return ( f"Potentially Stolen Okta Session - " - f"{deep_get(event, 'actor', 'displayName', default='Unknown_user')}" + f"{event.deep_get('actor', 'displayName', default='Unknown_user')}" ) diff --git a/rules/okta_rules/okta_support_reset.py b/rules/okta_rules/okta_support_reset.py index 64aff6c39..a3737e201 100644 --- a/rules/okta_rules/okta_support_reset.py +++ b/rules/okta_rules/okta_support_reset.py @@ -1,4 +1,4 @@ -from panther_base_helpers import deep_get, okta_alert_context +from panther_base_helpers import okta_alert_context OKTA_SUPPORT_RESET_EVENTS = [ "user.account.reset_password", @@ -12,10 +12,10 @@ def rule(event): if event.get("eventType") not in OKTA_SUPPORT_RESET_EVENTS: return False return ( - deep_get(event, "actor", "alternateId") == "system@okta.com" - and deep_get(event, "transaction", "id") == "unknown" - and deep_get(event, "userAgent", "rawUserAgent") is None - and deep_get(event, "client", "geographicalContext", "country") is None + event.deep_get("actor", "alternateId") == "system@okta.com" + and event.deep_get("transaction", "id") == "unknown" + and event.deep_get("userAgent", "rawUserAgent") is None + and event.deep_get("client", "geographicalContext", "country") is None ) diff --git a/rules/okta_rules/okta_threatinsight_security_threat_detected.py b/rules/okta_rules/okta_threatinsight_security_threat_detected.py index 11644629d..a0abf16aa 100644 --- a/rules/okta_rules/okta_threatinsight_security_threat_detected.py +++ b/rules/okta_rules/okta_threatinsight_security_threat_detected.py @@ -1,4 +1,4 @@ -from panther_base_helpers import deep_get, okta_alert_context +from panther_base_helpers import okta_alert_context def severity_from_threat_string(threat_detection): @@ -30,7 +30,7 @@ def title(event): def severity(event): - outcome = deep_get(event, "outcome", "result", default="") + outcome = event.deep_get("outcome", "result", default="") if outcome == "DENY": return "INFO" threat_detection = ( diff --git a/rules/onepassword_rules/onepassword_lut_sensitive_item_access.py b/rules/onepassword_rules/onepassword_lut_sensitive_item_access.py index 09f74faba..7090a1391 100644 --- a/rules/onepassword_rules/onepassword_lut_sensitive_item_access.py +++ b/rules/onepassword_rules/onepassword_lut_sensitive_item_access.py @@ -8,28 +8,26 @@ The steps detailed in that document are required for this rule to function as intended. """ -from panther_base_helpers import deep_get - # Add the human-readable names of 1Password items you want to monitor SENSITIVE_ITEM_WATCHLIST = ["demo_item"] def rule(event): return ( - deep_get(event, "p_enrichment", "1Password Translation", "item_uuid", "title") + event.deep_get("p_enrichment", "1Password Translation", "item_uuid", "title") in SENSITIVE_ITEM_WATCHLIST ) def title(event): - return f"A Sensitive 1Password Item was Accessed by user {deep_get(event, 'user', 'name')}" + return f"A Sensitive 1Password Item was Accessed by user {event.deep_get('user', 'name')}" def alert_context(event): context = { - "user": deep_get(event, "user", "name"), - "item_name": deep_get(event, "p_enrichment", "1Password Translation", "item_uuid", "title"), - "client": deep_get(event, "client", "app_name"), + "user": event.deep_get("user", "name"), + "item_name": event.deep_get("p_enrichment", "1Password Translation", "item_uuid", "title"), + "client": event.deep_get("client", "app_name"), "ip_address": event.udm("source_ip"), "event_time": event.get("timestamp"), } diff --git a/rules/onepassword_rules/onepassword_sensitive_item_access.py b/rules/onepassword_rules/onepassword_sensitive_item_access.py index cfee63ed5..1767d9b8a 100644 --- a/rules/onepassword_rules/onepassword_sensitive_item_access.py +++ b/rules/onepassword_rules/onepassword_sensitive_item_access.py @@ -8,8 +8,6 @@ BETA - Sensitive 1Password Item Accessed (onepassword_lut_sensitive_item_access.py) """ -from panther_base_helpers import deep_get - SENSITIVE_ITEM_WATCHLIST = {"ecd1d435c26440dc930ddfbbef201a11": "demo_item"} @@ -18,14 +16,14 @@ def rule(event): def title(event): - return f"A Sensitive 1Password Item was Accessed by user {deep_get(event, 'user', 'name')}" + return f"A Sensitive 1Password Item was Accessed by user {event.deep_get('user', 'name')}" def alert_context(event): context = { - "user": deep_get(event, "user", "name"), - "item_name": deep_get(event, "p_enrichment", "1Password Translation", "item_uuid", "title"), - "client": deep_get(event, "client", "app_name"), + "user": event.deep_get("user", "name"), + "item_name": event.deep_get("p_enrichment", "1Password Translation", "item_uuid", "title"), + "client": event.deep_get("client", "app_name"), "ip_address": event.udm("source_ip"), "event_time": event.get("timestamp"), } diff --git a/rules/onepassword_rules/onepassword_unusual_client.py b/rules/onepassword_rules/onepassword_unusual_client.py index 4e273f56e..27aad2763 100644 --- a/rules/onepassword_rules/onepassword_unusual_client.py +++ b/rules/onepassword_rules/onepassword_unusual_client.py @@ -9,8 +9,6 @@ If this differs from your orginization's needs this rule can be edited to suit your environment """ -from panther_base_helpers import deep_get - def rule(event): client_allowlist = [ @@ -24,20 +22,20 @@ def rule(event): "1Password for Android", ] - return deep_get(event, "client", "app_name") not in client_allowlist + return event.deep_get("client", "app_name") not in client_allowlist def title(event): - return f"Unusual 1Password client - {deep_get(event, 'client', 'app_name')} detected" + return f"Unusual 1Password client - {event.deep_get('client', 'app_name')} detected" def alert_context(event): context = {} - context["user"] = deep_get(event, "target_user", "name", default="UNKNOWN_USER") + context["user"] = event.deep_get("target_user", "name", default="UNKNOWN_USER") context["user_email"] = event.udm("actor_user") context["ip_address"] = event.udm("source_ip") - context["client"] = deep_get(event, "client", "app_name", default="UNKNOWN_CLIENT") - context["OS"] = deep_get(event, "client", "os_name", default="UNKNOWN_OS") + context["client"] = event.deep_get("client", "app_name", default="UNKNOWN_CLIENT") + context["OS"] = event.deep_get("client", "os_name", default="UNKNOWN_OS") context["login_result"] = event.get("category") context["time_seen"] = event.get("timestamp") diff --git a/rules/osquery_rules/osquery_linux_aws_commands.py b/rules/osquery_rules/osquery_linux_aws_commands.py index d22d7b474..4ac7366ae 100644 --- a/rules/osquery_rules/osquery_linux_aws_commands.py +++ b/rules/osquery_rules/osquery_linux_aws_commands.py @@ -1,7 +1,5 @@ import shlex -from panther_base_helpers import deep_get - PLATFORM_IGNORE_LIST = {"darwin"} @@ -10,11 +8,11 @@ def rule(event): if ( event.get("action") != "added" or "shell_history" not in event.get("name") - or deep_get(event, "decorations", "platform") in PLATFORM_IGNORE_LIST + or event.deep_get("decorations", "platform") in PLATFORM_IGNORE_LIST ): return False - command = deep_get(event, "columns", "command") + command = event.deep_get("columns", "command") if not command: return False try: @@ -31,6 +29,6 @@ def rule(event): def title(event): return ( - f"User [{deep_get(event, 'columns', 'username', default='')}] issued an" + f"User [{event.deep_get('columns', 'username', default='')}] issued an" f" aws-cli command on [{event.get('hostIdentifier', '')}]" ) diff --git a/rules/osquery_rules/osquery_linux_logins_non_office.py b/rules/osquery_rules/osquery_linux_logins_non_office.py index 71ce4270b..c473f8fb6 100644 --- a/rules/osquery_rules/osquery_linux_logins_non_office.py +++ b/rules/osquery_rules/osquery_linux_logins_non_office.py @@ -1,7 +1,5 @@ import ipaddress -from panther_base_helpers import deep_get - # This is only an example network, but you can set it to whatever you'd like OFFICE_NETWORKS = [ ipaddress.ip_network("192.168.1.100/32"), @@ -25,7 +23,7 @@ def rule(event): if "logged_in_users" in event.get("name"): # Only pay attention to users and not system-level accounts - if deep_get(event, "columns", "type") != "user": + if event.deep_get("columns", "type") != "user": return False elif "last" in event.get("name"): pass @@ -33,12 +31,12 @@ def rule(event): # A query we don't care about return False - host_ip = deep_get(event, "columns", "host") + host_ip = event.deep_get("columns", "host") return _login_from_non_office_network(host_ip) def title(event): - user = deep_get(event, "columns", "user", default=deep_get(event, "columns", "username")) + user = event.deep_get("columns", "user", default=event.deep_get("columns", "username")) return ( f"User [{user if user else ''}" diff --git a/rules/osquery_rules/osquery_mac_application_firewall.py b/rules/osquery_rules/osquery_mac_application_firewall.py index 2a42c5f53..c7c30d55c 100644 --- a/rules/osquery_rules/osquery_mac_application_firewall.py +++ b/rules/osquery_rules/osquery_mac_application_firewall.py @@ -1,5 +1,3 @@ -from panther_base_helpers import deep_get - QUERIES = {"pack_incident-response_alf", "pack/mac-cis/ApplicationFirewall"} @@ -14,10 +12,10 @@ def rule(event): # 0 If the firewall is disabled # 1 If the firewall is enabled with exceptions # 2 If the firewall is configured to block all incoming connections - int(deep_get(event, "columns", "global_state")) == 0 + int(event.deep_get("columns", "global_state")) == 0 or # Stealth mode is a best practice to avoid responding to unsolicited probes - int(deep_get(event, "columns", "stealth_enabled")) == 0 + int(event.deep_get("columns", "stealth_enabled")) == 0 ) diff --git a/rules/osquery_rules/osquery_mac_enable_auto_update.py b/rules/osquery_rules/osquery_mac_enable_auto_update.py index 2abe92757..3b41a1a1f 100644 --- a/rules/osquery_rules/osquery_mac_enable_auto_update.py +++ b/rules/osquery_rules/osquery_mac_enable_auto_update.py @@ -1,13 +1,10 @@ -from panther_base_helpers import deep_get - - def rule(event): return ( "SoftwareUpdate" in event.get("name", []) and event.get("action") == "added" - and deep_get(event, "columns", "domain") == "com.apple.SoftwareUpdate" - and deep_get(event, "columns", "key") == "AutomaticCheckEnabled" + and event.deep_get("columns", "domain") == "com.apple.SoftwareUpdate" + and event.deep_get("columns", "key") == "AutomaticCheckEnabled" and # Send an alert if not set to "true" - deep_get(event, "columns", "value") == "false" + event.deep_get("columns", "value") == "false" ) diff --git a/rules/osquery_rules/osquery_mac_osx_attacks_keyboard_events.py b/rules/osquery_rules/osquery_mac_osx_attacks_keyboard_events.py index b5fe9b42e..9fe7592eb 100644 --- a/rules/osquery_rules/osquery_mac_osx_attacks_keyboard_events.py +++ b/rules/osquery_rules/osquery_mac_osx_attacks_keyboard_events.py @@ -1,7 +1,5 @@ from fnmatch import fnmatch -from panther_base_helpers import deep_get - # sip protects against writing malware into the paths below. # additional apps can be added to this list based on your environments. # @@ -24,11 +22,11 @@ def rule(event): if event.get("action") != "added": return False - process_path = deep_get(event, "columns", "path", default="") + process_path = event.deep_get("columns", "path", default="") if process_path == "": return False - if deep_get(event, "columns", "name") in APPROVED_APPLICATION_NAMES: + if event.deep_get("columns", "name") in APPROVED_APPLICATION_NAMES: return False # Alert if the process is running outside any of the approved paths diff --git a/rules/osquery_rules/osquery_outdated.py b/rules/osquery_rules/osquery_outdated.py index 7e9005acf..d4f7cdce5 100644 --- a/rules/osquery_rules/osquery_outdated.py +++ b/rules/osquery_rules/osquery_outdated.py @@ -1,15 +1,13 @@ -from panther_base_helpers import deep_get - LATEST_VERSION = "5.10.2" def rule(event): return ( event.get("name") == "pack_it-compliance_osquery_info" - and deep_get(event, "columns", "version") != LATEST_VERSION + and event.deep_get("columns", "version") != LATEST_VERSION and event.get("action") == "added" ) def title(event): - return f"Osquery Version {deep_get(event, 'columns', 'version')} is Outdated" + return f"Osquery Version {event.deep_get('columns', 'version')} is Outdated" diff --git a/rules/osquery_rules/osquery_outdated_macos.py b/rules/osquery_rules/osquery_outdated_macos.py index e6e780bdf..b68937edc 100644 --- a/rules/osquery_rules/osquery_outdated_macos.py +++ b/rules/osquery_rules/osquery_outdated_macos.py @@ -1,5 +1,3 @@ -from panther_base_helpers import deep_get - SUPPORTED_VERSIONS = [ "10.15.1", "10.15.2", @@ -10,7 +8,7 @@ def rule(event): return ( event.get("name") == "pack_vuln-management_os_version" - and deep_get(event, "columns", "platform") == "darwin" - and deep_get(event, "columns", "version") not in SUPPORTED_VERSIONS + and event.deep_get("columns", "platform") == "darwin" + and event.deep_get("columns", "version") not in SUPPORTED_VERSIONS and event.get("action") == "added" ) diff --git a/rules/osquery_rules/osquery_ssh_listener.py b/rules/osquery_rules/osquery_ssh_listener.py index a5c49c681..9b5f5dad1 100644 --- a/rules/osquery_rules/osquery_ssh_listener.py +++ b/rules/osquery_rules/osquery_ssh_listener.py @@ -1,9 +1,6 @@ -from panther_base_helpers import deep_get - - def rule(event): return ( event.get("name") == "pack_incident-response_listening_ports" - and deep_get(event, "columns", "port") == "22" + and event.deep_get("columns", "port") == "22" and event.get("action") == "added" ) diff --git a/rules/osquery_rules/osquery_suspicious_cron.py b/rules/osquery_rules/osquery_suspicious_cron.py index 8f56638e7..1a9f02650 100644 --- a/rules/osquery_rules/osquery_suspicious_cron.py +++ b/rules/osquery_rules/osquery_suspicious_cron.py @@ -1,8 +1,6 @@ import shlex from fnmatch import fnmatch -from panther_base_helpers import deep_get - SUSPICIOUS_CRON_CMD_ARGS = { # Running in unexpected locations "/tmp/*", # nosec @@ -37,7 +35,7 @@ def rule(event): if "crontab" not in event.get("name"): return False - command = deep_get(event, "columns", "command") + command = event.deep_get("columns", "command") if not command: return False diff --git a/rules/panther_audit_rules/panther_detection_deleted.py b/rules/panther_audit_rules/panther_detection_deleted.py index 57452e95e..e6889538b 100644 --- a/rules/panther_audit_rules/panther_detection_deleted.py +++ b/rules/panther_audit_rules/panther_detection_deleted.py @@ -1,5 +1,3 @@ -from panther_base_helpers import deep_get - PANTHER_DETECTION_DELETE_ACTIONS = [ "DELETE_DATA_MODEL", "DELETE_DETECTION", @@ -22,9 +20,9 @@ def title(event): def alert_context(event): - detections_list = deep_get(event, "actionParams", "dynamic", "input", "detections") + detections_list = event.deep_get("actionParams", "dynamic", "input", "detections") if detections_list is None: - detections_list = deep_get(event, "actionParams", "input", "detections", default=[]) + detections_list = event.deep_get("actionParams", "input", "detections", default=[]) return { "deleted_detections_list": [x.get("id") for x in detections_list], "user": event.udm("actor_user"), diff --git a/rules/panther_audit_rules/panther_sensitive_role_created.py b/rules/panther_audit_rules/panther_sensitive_role_created.py index 682788310..e54735172 100644 --- a/rules/panther_audit_rules/panther_sensitive_role_created.py +++ b/rules/panther_audit_rules/panther_sensitive_role_created.py @@ -1,5 +1,4 @@ import panther_event_type_helpers as event_type -from panther_base_helpers import deep_get PANTHER_ADMIN_PERMISSIONS = [ "UserModify", @@ -17,9 +16,9 @@ def rule(event): if event.udm("event_type") not in PANTHER_ROLE_ACTIONS: return False - permissions = deep_get(event, "actionParams", "dynamic", "input", "permissions") + permissions = event.deep_get("actionParams", "dynamic", "input", "permissions") if permissions is None: - deep_get(event, "actionParams", "input", "permissions", default="") + event.deep_get("actionParams", "input", "permissions", default="") role_permissions = set(permissions) return ( @@ -29,9 +28,9 @@ def rule(event): def title(event): - role_name = deep_get(event, "actionParams", "dynamic", "input", "name") + role_name = event.deep_get("actionParams", "dynamic", "input", "name") if role_name is None: - role_name = deep_get(event, "actionParams", "input", "name", default="") + role_name = event.deep_get("actionParams", "input", "name", default="") return ( f"Role with Admin Permissions created by {event.udm('actor_user')}" f"Role Name: {role_name}" @@ -41,6 +40,6 @@ def title(event): def alert_context(event): return { "user": event.udm("actor_user"), - "role_name": deep_get(event, "actionParams", "name"), + "role_name": event.deep_get("actionParams", "name"), "ip": event.udm("source_ip"), } diff --git a/rules/panther_audit_rules/panther_sensitive_role_created.yml b/rules/panther_audit_rules/panther_sensitive_role_created.yml index 68979bf87..602c2dddf 100644 --- a/rules/panther_audit_rules/panther_sensitive_role_created.yml +++ b/rules/panther_audit_rules/panther_sensitive_role_created.yml @@ -175,7 +175,7 @@ Tests: "p_log_type": "Panther.Audit", "p_parse_time": "2023-02-09 21:46:53.858602089", "p_row_id": "b29dff36ad73cb77a5d7a3a816c39c2a", - "p_rule_error": '''NoneType'' object is not iterable: Panther.Sensitive.Role.py, line 20, in rule role_permissions = set(deep_get(event, "actionParams", "input", "permissions"))', + "p_rule_error": '''NoneType'' object is not iterable: Panther.Sensitive.Role.py, line 20, in rule role_permissions = set(event.deep_get("actionParams", "input", "permissions"))', "p_rule_id": "Panther.Sensitive.Role", "p_rule_reports": { "MITRE ATT&CK": ["TA0003:T1098"] }, "p_rule_severity": "HIGH", diff --git a/rules/panther_audit_rules/panther_user_modified.py b/rules/panther_audit_rules/panther_user_modified.py index 3c0d191a1..a343d911b 100644 --- a/rules/panther_audit_rules/panther_user_modified.py +++ b/rules/panther_audit_rules/panther_user_modified.py @@ -1,5 +1,4 @@ import panther_event_type_helpers as event_type -from panther_base_helpers import deep_get PANTHER_USER_ACTIONS = [ event_type.USER_ACCOUNT_MODIFIED, @@ -13,18 +12,18 @@ def rule(event): def title(event): - change_target = deep_get(event, "actionParams", "dynamic", "input", "email") + change_target = event.deep_get("actionParams", "dynamic", "input", "email") if change_target is None: - change_target = deep_get(event, "actionParams", "input", "email") + change_target = event.deep_get("actionParams", "input", "email") if change_target is None: - change_target = deep_get(event, "actionParams", "email", default="") + change_target = event.deep_get("actionParams", "email", default="") return f"The user account " f"{change_target} " f"was modified by {event.udm('actor_user')}" def alert_context(event): - change_target = deep_get(event, "actionParams", "dynamic", "input", "email") + change_target = event.deep_get("actionParams", "dynamic", "input", "email") if change_target is None: - change_target = deep_get(event, "actionParams", "input", "email", default="") + change_target = event.deep_get("actionParams", "input", "email", default="") return { "user": event.udm("actor_user"), "change_target": change_target, diff --git a/rules/sentinelone_rules/sentinelone_alert_passthrough.py b/rules/sentinelone_rules/sentinelone_alert_passthrough.py index 8ae2fe0cf..78bedda3e 100644 --- a/rules/sentinelone_rules/sentinelone_alert_passthrough.py +++ b/rules/sentinelone_rules/sentinelone_alert_passthrough.py @@ -1,5 +1,3 @@ -from panther_base_helpers import deep_get - SENTINELONE_SEVERITY = { "E_LOW": "LOW", "E_MEDIUM": "MEDIUM", @@ -16,8 +14,8 @@ def rule(event): def title(event): return ( "SentinelOne " - f"[{SENTINELONE_SEVERITY.get(deep_get(event,'data', 'severity', default=''))}] " - f"Alert - [{deep_get(event, 'data', 'rulename')}]" + f"[{SENTINELONE_SEVERITY.get(event.deep_get('data', 'severity', default=''))}] " + f"Alert - [{event.deep_get('data', 'rulename')}]" ) @@ -26,7 +24,7 @@ def dedup(event): def severity(event): - return SENTINELONE_SEVERITY.get(deep_get(event, "data", "severity", default=""), "MEDIUM") + return SENTINELONE_SEVERITY.get(event.deep_get("data", "severity", default=""), "MEDIUM") def alert_context(event): diff --git a/rules/sentinelone_rules/sentinelone_threats.py b/rules/sentinelone_rules/sentinelone_threats.py index d42792d44..0bc3aa875 100644 --- a/rules/sentinelone_rules/sentinelone_threats.py +++ b/rules/sentinelone_rules/sentinelone_threats.py @@ -1,5 +1,3 @@ -from panther_base_helpers import deep_get - NEW_THREAT_ACTIVITYTYPES = [ 19, # New Malicious Threat Not Mitigated 4108, # New Malicious Threat Not Mitigated @@ -14,9 +12,9 @@ def rule(event): def title(event): return ( - f"SentinelOne - [{deep_get(event, 'data', 'confidencelevel', default='')}] level " - f"[{deep_get(event, 'data', 'threatclassification' ,default='')}] threat detected from " - f"[{deep_get(event, 'data', 'threatclassificationsource', default= '')}]." + f"SentinelOne - [{event.deep_get('data', 'confidencelevel', default='')}] level " + f"[{event.deep_get('data', 'threatclassification' ,default='')}] threat detected from " + f"[{event.deep_get('data', 'threatclassificationsource', default= '')}]." ) @@ -25,7 +23,7 @@ def dedup(event): def severity(event): - if deep_get(event, "data", "confidencelevel", default="") == "malicious": + if event.deep_get("data", "confidencelevel", default="") == "malicious": return "CRITICAL" return "HIGH" diff --git a/rules/slack_rules/slack_app_access_expanded.py b/rules/slack_rules/slack_app_access_expanded.py index 0003f5ddb..c93d9dd01 100644 --- a/rules/slack_rules/slack_app_access_expanded.py +++ b/rules/slack_rules/slack_app_access_expanded.py @@ -1,4 +1,4 @@ -from panther_base_helpers import deep_get, slack_alert_context +from panther_base_helpers import slack_alert_context ACCESS_EXPANDED_ACTIONS = [ "app_scopes_expanded", @@ -14,8 +14,8 @@ def rule(event): def title(event): return ( - f"Slack App [{deep_get(event, 'entity', 'app', 'name')}] " - f"Access Expanded by [{deep_get(event, 'actor', 'user', 'name')}]" + f"Slack App [{event.deep_get('entity', 'app', 'name')}] " + f"Access Expanded by [{event.deep_get('actor', 'user', 'name')}]" ) @@ -23,8 +23,8 @@ def alert_context(event): context = slack_alert_context(event) # Diff previous and new scopes - new_scopes = deep_get(event, "details", "new_scopes", default=[]) - prv_scopes = deep_get(event, "details", "previous_scopes", default=[]) + new_scopes = event.deep_get("details", "new_scopes", default=[]) + prv_scopes = event.deep_get("details", "previous_scopes", default=[]) context["scopes_added"] = [x for x in new_scopes if x not in prv_scopes] context["scoped_removed"] = [x for x in prv_scopes if x not in new_scopes] @@ -35,14 +35,14 @@ def alert_context(event): def severity(event): # Used to escalate to High/Critical if the app is granted admin privileges # May want to escalate to "Critical" depending on security posture - if "admin" in deep_get(event, "entity", "app", "scopes", default=[]): + if "admin" in event.deep_get("entity", "app", "scopes", default=[]): return "High" # Fallback method in case the admin scope is not directly mentioned in entity for whatever - if "admin" in deep_get(event, "details", "new_scope", default=[]): + if "admin" in event.deep_get("details", "new_scope", default=[]): return "High" - if "admin" in deep_get(event, "details", "bot_scopes", default=[]): + if "admin" in event.deep_get("details", "bot_scopes", default=[]): return "High" return "Medium" diff --git a/rules/slack_rules/slack_app_added.py b/rules/slack_rules/slack_app_added.py index ad006ffe8..d57357ff6 100644 --- a/rules/slack_rules/slack_app_added.py +++ b/rules/slack_rules/slack_app_added.py @@ -1,4 +1,4 @@ -from panther_base_helpers import deep_get, slack_alert_context +from panther_base_helpers import slack_alert_context APP_ADDED_ACTIONS = [ "app_approved", @@ -13,14 +13,14 @@ def rule(event): def title(event): return ( - f"Slack App [{deep_get(event, 'entity', 'app', 'name')}] " - f"Added by [{deep_get(event, 'actor', 'user', 'name')}]" + f"Slack App [{event.deep_get('entity', 'app', 'name')}] " + f"Added by [{event.deep_get('actor', 'user', 'name')}]" ) def alert_context(event): context = slack_alert_context(event) - context["scopes"] = deep_get(event, "entity", "scopes") + context["scopes"] = event.deep_get("entity", "scopes") return context @@ -28,14 +28,14 @@ def alert_context(event): def severity(event): # Used to escalate to High/Critical if the app is granted admin privileges # May want to escalate to "Critical" depending on security posture - if "admin" in deep_get(event, "entity", "app", "scopes", default=[]): + if "admin" in event.deep_get("entity", "app", "scopes", default=[]): return "High" # Fallback method in case the admin scope is not directly mentioned in entity for whatever - if "admin" in deep_get(event, "details", "new_scope", default=[]): + if "admin" in event.deep_get("details", "new_scope", default=[]): return "High" - if "admin" in deep_get(event, "details", "bot_scopes", default=[]): + if "admin" in event.deep_get("details", "bot_scopes", default=[]): return "High" return "Medium" diff --git a/rules/slack_rules/slack_app_removed.py b/rules/slack_rules/slack_app_removed.py index effe84cad..7583404f9 100644 --- a/rules/slack_rules/slack_app_removed.py +++ b/rules/slack_rules/slack_app_removed.py @@ -1,4 +1,4 @@ -from panther_base_helpers import deep_get, slack_alert_context +from panther_base_helpers import slack_alert_context APP_REMOVED_ACTIONS = [ "app_restricted", @@ -13,8 +13,8 @@ def rule(event): def title(event): return ( - f"Slack App [{deep_get(event, 'entity', 'app', 'name')}] " - f"Removed by [{deep_get(event, 'actor', 'user', 'name')}]" + f"Slack App [{event.deep_get('entity', 'app', 'name')}] " + f"Removed by [{event.deep_get('actor', 'user', 'name')}]" ) diff --git a/rules/slack_rules/slack_application_dos.py b/rules/slack_rules/slack_application_dos.py index 54add00f0..ca37d82a2 100644 --- a/rules/slack_rules/slack_application_dos.py +++ b/rules/slack_rules/slack_application_dos.py @@ -1,4 +1,4 @@ -from panther_base_helpers import deep_get, slack_alert_context +from panther_base_helpers import slack_alert_context DENIAL_OF_SERVICE_ACTIONS = [ "bulk_session_reset_by_admin", @@ -16,7 +16,7 @@ def rule(event): def dedup(event): - return f"Slack.AuditLogs.ApplicationDoS{deep_get(event, 'entity', 'user', 'name')}" + return f"Slack.AuditLogs.ApplicationDoS{event.deep_get('entity', 'user', 'name')}" def alert_context(event): diff --git a/rules/slack_rules/slack_legal_hold_policy_modified.py b/rules/slack_rules/slack_legal_hold_policy_modified.py index 26fc66eb3..afb4f401f 100644 --- a/rules/slack_rules/slack_legal_hold_policy_modified.py +++ b/rules/slack_rules/slack_legal_hold_policy_modified.py @@ -1,4 +1,4 @@ -from panther_base_helpers import deep_get, slack_alert_context +from panther_base_helpers import slack_alert_context LEGAL_HOLD_POLICY_ACTIONS = { "legal_hold_policy_entities_deleted": "Slack Legal Hold Policy Entities Deleted", @@ -17,7 +17,7 @@ def title(event): if event.get("action") == "legal_hold_policy_updated": return ( f"Slack Legal Hold Updated " - f"[{deep_get(event, 'details', 'old_legal_hold_policy', 'name')}]" + f"[{event.deep_get('details', 'old_legal_hold_policy', 'name')}]" ) if event.get("action") in LEGAL_HOLD_POLICY_ACTIONS: return LEGAL_HOLD_POLICY_ACTIONS.get(event.get("action")) diff --git a/rules/slack_rules/slack_privilege_changed_to_user.py b/rules/slack_rules/slack_privilege_changed_to_user.py index 8cb636417..44e52a976 100644 --- a/rules/slack_rules/slack_privilege_changed_to_user.py +++ b/rules/slack_rules/slack_privilege_changed_to_user.py @@ -1,4 +1,4 @@ -from panther_base_helpers import deep_get, slack_alert_context +from panther_base_helpers import slack_alert_context def rule(event): @@ -6,8 +6,8 @@ def rule(event): def title(event): - username = deep_get(event, "entity", "user", "name", default="") - email = deep_get(event, "entity", "user", "email", default="") + username = event.deep_get("entity", "user", "name", default="") + email = event.deep_get("entity", "user", "email", default="") return f"Slack {username}'s ({email}) role changed to User" diff --git a/rules/slack_rules/slack_user_privilege_escalation.py b/rules/slack_rules/slack_user_privilege_escalation.py index a810df427..09ae231b4 100644 --- a/rules/slack_rules/slack_user_privilege_escalation.py +++ b/rules/slack_rules/slack_user_privilege_escalation.py @@ -1,4 +1,4 @@ -from panther_base_helpers import deep_get, slack_alert_context +from panther_base_helpers import slack_alert_context USER_PRIV_ESC_ACTIONS = { "owner_transferred": "Slack Owner Transferred", @@ -14,11 +14,11 @@ def rule(event): def title(event): # This is the user taking the action. - actor_username = deep_get(event, "actor", "user", "name", default="") - actor_email = deep_get(event, "actor", "user", "email", default="") + actor_username = event.deep_get("actor", "user", "name", default="") + actor_email = event.deep_get("actor", "user", "email", default="") # This is the user the action is taken on. - entity_username = deep_get(event, "entity", "user", "name", default="") - entity_email = deep_get(event, "entity", "user", "email", default="") + entity_username = event.deep_get("entity", "user", "name", default="") + entity_email = event.deep_get("entity", "user", "email", default="") action = event.get("action") if action == "owner_transferred": return f"{USER_PRIV_ESC_ACTIONS[action]} from {actor_username} ({actor_email})" diff --git a/rules/snyk_rules/snyk_misc_settings.py b/rules/snyk_rules/snyk_misc_settings.py index 5fead94f4..33994e18f 100644 --- a/rules/snyk_rules/snyk_misc_settings.py +++ b/rules/snyk_rules/snyk_misc_settings.py @@ -10,14 +10,14 @@ def rule(event): if not filter_include_event(event): return False - action = event.deep_get("event", default="") + action = event.get("event", "") return action in ACTIONS def title(event): group_or_org = "" operation = "" - action = event.deep_get("event", default="") + action = event.get("event", "") if "." in action: group_or_org = action.split(".")[0].title() operation = ".".join(action.split(".")[1:]).title() diff --git a/rules/snyk_rules/snyk_org_settings.py b/rules/snyk_rules/snyk_org_settings.py index e4d972f1a..01dded114 100644 --- a/rules/snyk_rules/snyk_org_settings.py +++ b/rules/snyk_rules/snyk_org_settings.py @@ -17,14 +17,14 @@ def rule(event): if not filter_include_event(event): return False - action = event.deep_get("event", default="") + action = event.get("event", "") return action in ACTIONS def title(event): group_or_org = "" operation = "" - action = event.deep_get("event", default="") + action = event.get("event", "") if "." in action: group_or_org = action.split(".")[0].title() operation = ".".join(action.split(".")[1:]).title() diff --git a/rules/snyk_rules/snyk_ou_change.py b/rules/snyk_rules/snyk_ou_change.py index af7111e67..10c993992 100644 --- a/rules/snyk_rules/snyk_ou_change.py +++ b/rules/snyk_rules/snyk_ou_change.py @@ -20,13 +20,13 @@ def rule(event): if not filter_include_event(event): return False - action = event.deep_get("event", default="") + action = event.get("event", "") return action in ACTIONS def title(event): group_or_org = "" - action = event.deep_get("event", default="") + action = event.get("event", "") if "." in action: group_or_org = action.split(".")[0].title() return ( @@ -50,7 +50,7 @@ def dedup(event): def severity(event): - action = event.deep_get("event", default="") + action = event.get("event", "") if action.endswith((".remove", ".delete")): return "HIGH" if action.endswith((".edit")): diff --git a/rules/snyk_rules/snyk_project_settings.py b/rules/snyk_rules/snyk_project_settings.py index 5b2cff1d6..495ddfe59 100644 --- a/rules/snyk_rules/snyk_project_settings.py +++ b/rules/snyk_rules/snyk_project_settings.py @@ -34,14 +34,14 @@ def rule(event): return False if event.deep_get("content", "after", "description") == "No new Code Analysis issues found": return False - action = event.deep_get("event", default="") + action = event.get("event", "") return action in ACTIONS def title(event): group_or_org = "" operation = "" - action = event.deep_get("event", default="") + action = event.get("event", "") if "." in action: group_or_org = action.split(".")[0].title() operation = ".".join(action.split(".")[1:]).title() @@ -69,7 +69,7 @@ def dedup(event): def severity(event): - action = event.deep_get("event", default="") + action = event.get("event", "") if action == "org.project.fix_pr.manual_open": return "INFO" return "LOW" diff --git a/rules/snyk_rules/snyk_role_change.py b/rules/snyk_rules/snyk_role_change.py index b345077c0..e5bca399a 100644 --- a/rules/snyk_rules/snyk_role_change.py +++ b/rules/snyk_rules/snyk_role_change.py @@ -18,14 +18,14 @@ def rule(event): if not filter_include_event(event): return False - action = event.deep_get("event", default="") + action = event.get("event", "") return action in ACTIONS def title(event): group_or_org = "" crud_operation = "" - action = event.deep_get("event", default="") + action = event.get("event", "") if "." in action: group_or_org = action.split(".")[0].title() crud_operation = action.split(".")[-1].title() @@ -39,7 +39,7 @@ def title(event): def alert_context(event): a_c = snyk_alert_context(event) role = event.deep_get("content", "after", "role", default=None) - if not role and "afterRoleName" in event.deep_get("content", default={}): + if not role and "afterRoleName" in event.get("content", {}): role = event.deep_get("content", "afterRoleName", default=None) if role: a_c["role_permission"] = role @@ -57,7 +57,7 @@ def dedup(event): def severity(event): role = event.deep_get("content", "after", "role", default=None) - if not role and "afterRoleName" in event.deep_get("content", default={}): + if not role and "afterRoleName" in event.get("content", {}): role = event.deep_get("content", "afterRoleName", default=None) if role == "ADMIN": return "CRITICAL" diff --git a/rules/snyk_rules/snyk_svcacct_change.py b/rules/snyk_rules/snyk_svcacct_change.py index d301009f6..904bbf028 100644 --- a/rules/snyk_rules/snyk_svcacct_change.py +++ b/rules/snyk_rules/snyk_svcacct_change.py @@ -15,14 +15,14 @@ def rule(event): if not filter_include_event(event): return False - action = event.deep_get("event", default="") + action = event.get("event", "") return action in ACTIONS def title(event): group_or_org = "" crud_operation = "" - action = event.deep_get("event", default="") + action = event.get("event", "") if "." in action: group_or_org = action.split(".")[0].title() crud_operation = action.split(".")[-1].title() @@ -53,7 +53,7 @@ def dedup(event): def severity(event): - action = event.deep_get("event", default="") + action = event.get("event", "") role = event.deep_get("content", "role", "role", default=None) if not role: role = event.deep_get("content", "role", default=None) diff --git a/rules/snyk_rules/snyk_system_externalaccess.py b/rules/snyk_rules/snyk_system_externalaccess.py index d2dc43aa9..f375be05e 100644 --- a/rules/snyk_rules/snyk_system_externalaccess.py +++ b/rules/snyk_rules/snyk_system_externalaccess.py @@ -10,13 +10,13 @@ def rule(event): if not filter_include_event(event): return False - action = event.deep_get("event", default="") + action = event.get("event", "") return action in ACTIONS def title(event): current_setting = event.deep_get("content", "after", "isEnabled", default=False) - action = event.deep_get("event", default="") + action = event.get("event", "") if "." in action: action = action.split(".")[0].title() return ( diff --git a/rules/snyk_rules/snyk_system_policysetting.py b/rules/snyk_rules/snyk_system_policysetting.py index b5844c1f8..6ff512d59 100644 --- a/rules/snyk_rules/snyk_system_policysetting.py +++ b/rules/snyk_rules/snyk_system_policysetting.py @@ -13,7 +13,7 @@ def rule(event): if not filter_include_event(event): return False - action = event.deep_get("event", default="") + action = event.get("event", "") return action in ACTIONS diff --git a/rules/snyk_rules/snyk_system_sso.py b/rules/snyk_rules/snyk_system_sso.py index 41bd7aded..07590bdbc 100644 --- a/rules/snyk_rules/snyk_system_sso.py +++ b/rules/snyk_rules/snyk_system_sso.py @@ -12,7 +12,7 @@ def rule(event): if not filter_include_event(event): return False - action = event.deep_get("event", default="") + action = event.get("event", "") return action in ACTIONS diff --git a/rules/snyk_rules/snyk_user_mgmt.py b/rules/snyk_rules/snyk_user_mgmt.py index f2201bf7b..debcceb0e 100644 --- a/rules/snyk_rules/snyk_user_mgmt.py +++ b/rules/snyk_rules/snyk_user_mgmt.py @@ -25,13 +25,13 @@ def rule(event): if not filter_include_event(event): return False - action = event.deep_get("event", default="") + action = event.get("event", "") # for org.user.add/group.user.add via SAML/SCIM # the attributes .userId and .content.publicUserId # have the same value if action.endswith(".user.add"): target_user = event.deep_get("content", "userPublicId", default="") - actor = event.deep_get("userId", default="") + actor = event.get("userId", "") if target_user == actor: return False return action in ACTIONS @@ -40,7 +40,7 @@ def rule(event): def title(event): group_or_org = "" operation = "" - action = event.deep_get("event", default="") + action = event.get("event", "") if "." in action: group_or_org = action.split(".")[0].title() operation = ".".join(action.split(".")[2:]).title() @@ -66,7 +66,7 @@ def dedup(event): def severity(event): role = event.deep_get("content", "after", "role", default=None) - if not role and "afterRoleName" in event.deep_get("content", default={}): + if not role and "afterRoleName" in event.get("content", {}): role = event.deep_get("content", "afterRoleName", default=None) if role == "ADMIN": return "CRITICAL" diff --git a/rules/standard_rules/impossible_travel_login.py b/rules/standard_rules/impossible_travel_login.py index 1850307c3..f5b2694e2 100644 --- a/rules/standard_rules/impossible_travel_login.py +++ b/rules/standard_rules/impossible_travel_login.py @@ -19,7 +19,7 @@ def gen_key(event): The data_model needs to answer to "actor_user" """ - rule_name = deep_get(event, "p_source_label") + rule_name = event.get("p_source_label") actor = event.udm("actor_user") if None in [rule_name, actor]: return None @@ -45,7 +45,7 @@ def rule(event): if event.udm("event_type") != event_type.SUCCESSFUL_LOGIN: return False - p_event_datetime = resolve_timestamp_string(deep_get(event, "p_event_time")) + p_event_datetime = resolve_timestamp_string(event.get("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 @@ -165,7 +165,7 @@ def rule(event): def title(event): # - log_source = deep_get(event, "p_source_label", default="") + log_source = event.get("p_source_label", "") 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="") diff --git a/rules/tailscale_rules/tailscale_https_disabled.py b/rules/tailscale_rules/tailscale_https_disabled.py index a3361e569..67074d6e4 100644 --- a/rules/tailscale_rules/tailscale_https_disabled.py +++ b/rules/tailscale_rules/tailscale_https_disabled.py @@ -1,14 +1,13 @@ from global_filter_tailscale import filter_include_event -from panther_base_helpers import deep_get from panther_tailscale_helpers import is_tailscale_admin_console_event, tailscale_alert_context def rule(event): if not filter_include_event(event): return False - action = deep_get(event, "event", "action", default="") - target_property = deep_get( - event, "event", "target", "property", default="" + action = event.deep_get("event", "action", default="") + target_property = event.deep_get( + "event", "target", "property", default="" ) return all( [ @@ -20,8 +19,8 @@ def rule(event): def title(event): - user = deep_get(event, "event", "actor", "loginName", default="") - target_id = deep_get(event, "event", "target", "id", default="") + user = event.deep_get("event", "actor", "loginName", default="") + target_id = event.deep_get("event", "target", "id", default="") return ( f"Tailscale user [{user}] disabled HTTPS for " f"[{target_id}] in your organization’s tenant." diff --git a/rules/tailscale_rules/tailscale_machine_approval_requirements_disabled.py b/rules/tailscale_rules/tailscale_machine_approval_requirements_disabled.py index 64d390f29..6ec015bcf 100644 --- a/rules/tailscale_rules/tailscale_machine_approval_requirements_disabled.py +++ b/rules/tailscale_rules/tailscale_machine_approval_requirements_disabled.py @@ -1,14 +1,13 @@ from global_filter_tailscale import filter_include_event -from panther_base_helpers import deep_get from panther_tailscale_helpers import is_tailscale_admin_console_event, tailscale_alert_context def rule(event): if not filter_include_event(event): return False - action = deep_get(event, "event", "action", default="") - target_property = deep_get( - event, "event", "target", "property", default="" + action = event.deep_get("event", "action", default="") + target_property = event.deep_get( + "event", "target", "property", default="" ) return all( [ @@ -20,7 +19,7 @@ def rule(event): def title(event): - user = deep_get(event, "event", "actor", "loginName", default="") + user = event.deep_get("event", "actor", "loginName", default="") return ( f"Tailscale user [{user}] disabled device approval requirements " f"for new devices accessing your organization’s network." diff --git a/rules/tailscale_rules/tailscale_magicdns_disabled.py b/rules/tailscale_rules/tailscale_magicdns_disabled.py index ad29e9aaf..4ee598b6d 100644 --- a/rules/tailscale_rules/tailscale_magicdns_disabled.py +++ b/rules/tailscale_rules/tailscale_magicdns_disabled.py @@ -1,14 +1,13 @@ from global_filter_tailscale import filter_include_event -from panther_base_helpers import deep_get from panther_tailscale_helpers import is_tailscale_admin_console_event, tailscale_alert_context def rule(event): if not filter_include_event(event): return False - action = deep_get(event, "event", "action", default="") - target_property = deep_get( - event, "event", "target", "property", default="" + action = event.deep_get("event", "action", default="") + target_property = event.deep_get( + "event", "target", "property", default="" ) return all( [ @@ -20,8 +19,8 @@ def rule(event): def title(event): - user = deep_get(event, "event", "actor", "loginName", default="") - target_id = deep_get(event, "event", "target", "id", default="") + user = event.deep_get("event", "actor", "loginName", default="") + target_id = event.deep_get("event", "target", "id", default="") return ( f"Tailscale user [{user}] disabled Magic DNS for " f"[{target_id}] in your organization’s tenant." diff --git a/rules/tines_rules/tines_actions_disabled_changes.py b/rules/tines_rules/tines_actions_disabled_changes.py index bc19bb6d5..9f991216a 100644 --- a/rules/tines_rules/tines_actions_disabled_changes.py +++ b/rules/tines_rules/tines_actions_disabled_changes.py @@ -1,5 +1,4 @@ from global_filter_tines import filter_include_event -from panther_base_helpers import deep_get from panther_tines_helpers import tines_alert_context ACTIONS = ["ActionsDisabledChange"] @@ -8,13 +7,13 @@ def rule(event): if not filter_include_event(event): return False - action = deep_get(event, "operation_name", default="") + action = event.get("operation_name", "") return action in ACTIONS def title(event): - action = deep_get(event, "operation_name", default="") - actor = deep_get(event, "user_email", default="") + action = event.get("operation_name", "") + actor = event.get("user_email", "") return f"Tines: {action} " f"by {actor}" diff --git a/rules/tines_rules/tines_custom_ca.py b/rules/tines_rules/tines_custom_ca.py index bdd2ead46..d1abfaaeb 100644 --- a/rules/tines_rules/tines_custom_ca.py +++ b/rules/tines_rules/tines_custom_ca.py @@ -1,5 +1,4 @@ from global_filter_tines import filter_include_event -from panther_base_helpers import deep_get from panther_tines_helpers import tines_alert_context ACTIONS = [ @@ -10,13 +9,13 @@ def rule(event): if not filter_include_event(event): return False - action = deep_get(event, "operation_name", default="") + action = event.get("operation_name", "") return action in ACTIONS def title(event): - action = deep_get(event, "operation_name", default="") - return f"Tines: [{action}] " f"by [{deep_get(event, 'user_email', default='')}]" + action = event.get("operation_name", "") + return f"Tines: [{action}] " f"by [{event.deep_get('user_email', default='')}]" def alert_context(event): @@ -25,7 +24,7 @@ def alert_context(event): def dedup(event): return ( - f"{deep_get(event, 'user_id', default='')}" + f"{event.deep_get('user_id', default='')}" "_" - f"{deep_get(event, 'operation_name', default='')}" + f"{event.deep_get('operation_name', default='')}" ) diff --git a/rules/tines_rules/tines_enqueued_retrying_job_deletion.py b/rules/tines_rules/tines_enqueued_retrying_job_deletion.py index 083dd3896..53ba39058 100644 --- a/rules/tines_rules/tines_enqueued_retrying_job_deletion.py +++ b/rules/tines_rules/tines_enqueued_retrying_job_deletion.py @@ -1,5 +1,4 @@ from global_filter_tines import filter_include_event -from panther_base_helpers import deep_get from panther_tines_helpers import tines_alert_context @@ -7,16 +6,16 @@ def rule(event): if not filter_include_event(event): return False - return deep_get(event, "operation_name", default="") in [ + return event.get("operation_name", "") in [ "JobsQueuedDeletion", "JobsRetryingDeletion", ] def title(event): - operation = deep_get(event, "operation_name", default="") - user = deep_get(event, "user_email", default="") - tines_instance = deep_get(event, "p_source_label", default="") + operation = event.get("operation_name", "") + user = event.get("user_email", "") + tines_instance = event.get("p_source_label", "") return f"Tines [{operation}] performed by [{user}] on [{tines_instance}]." diff --git a/rules/tines_rules/tines_global_resource_destruction.py b/rules/tines_rules/tines_global_resource_destruction.py index e60e23e33..2e74b0a0a 100644 --- a/rules/tines_rules/tines_global_resource_destruction.py +++ b/rules/tines_rules/tines_global_resource_destruction.py @@ -1,5 +1,4 @@ from global_filter_tines import filter_include_event -from panther_base_helpers import deep_get from panther_tines_helpers import tines_alert_context @@ -7,16 +6,13 @@ def rule(event): if not filter_include_event(event): return False - return ( - deep_get(event, "operation_name", default="") - == "GlobalResourceDestruction" - ) + return event.get("operation_name", "") == "GlobalResourceDestruction" def title(event): - operation = deep_get(event, "operation_name", default="") - user = deep_get(event, "user_email", default="") - tines_instance = deep_get(event, "p_source_label", default="") + operation = event.get("operation_name", "") + user = event.get("user_email", "") + tines_instance = event.get("p_source_label", "") return f"Tines [{operation}] performed by [{user}] on [{tines_instance}]." diff --git a/rules/tines_rules/tines_sso_settings.py b/rules/tines_rules/tines_sso_settings.py index c3f19168b..d24e928e7 100644 --- a/rules/tines_rules/tines_sso_settings.py +++ b/rules/tines_rules/tines_sso_settings.py @@ -1,5 +1,4 @@ from global_filter_tines import filter_include_event -from panther_base_helpers import deep_get from panther_tines_helpers import tines_alert_context ACTIONS = [ @@ -12,15 +11,15 @@ def rule(event): if not filter_include_event(event): return False - action = deep_get(event, "operation_name", default="") + action = event.get("operation_name", "") return action in ACTIONS def title(event): - action = deep_get(event, "operation_name", default="") + action = event.get("operation_name", "") return ( f"Tines: [{action}] Setting " - f"changed by [{deep_get(event, 'user_email', default='')}]" + f"changed by [{event.deep_get('user_email', default='')}]" ) @@ -30,7 +29,7 @@ def alert_context(event): def dedup(event): return ( - f"{deep_get(event, 'user_id', default='')}" + f"{event.deep_get('user_id', default='')}" "_" - f"{deep_get(event, 'operation_name', default='')}" + f"{event.deep_get('operation_name', default='')}" ) diff --git a/rules/tines_rules/tines_story_items_destruction.py b/rules/tines_rules/tines_story_items_destruction.py index 73685ab45..432254564 100644 --- a/rules/tines_rules/tines_story_items_destruction.py +++ b/rules/tines_rules/tines_story_items_destruction.py @@ -1,5 +1,4 @@ from global_filter_tines import filter_include_event -from panther_base_helpers import deep_get from panther_tines_helpers import tines_alert_context @@ -7,15 +6,13 @@ def rule(event): if not filter_include_event(event): return False - return ( - deep_get(event, "operation_name", default="") == "StoryItemsDestruction" - ) + return event.get("operation_name", "") == "StoryItemsDestruction" def title(event): - operation = deep_get(event, "operation_name", default="") - user = deep_get(event, "user_email", default="") - tines_instance = deep_get(event, "p_source_label", default="") + operation = event.get("operation_name", "") + user = event.get("user_email", "") + tines_instance = event.get("p_source_label", "") return f"Tines [{operation}] performed by [{user}] on [{tines_instance}]." diff --git a/rules/tines_rules/tines_story_jobs_clearance.py b/rules/tines_rules/tines_story_jobs_clearance.py index 3c93ba39d..f794cc69b 100644 --- a/rules/tines_rules/tines_story_jobs_clearance.py +++ b/rules/tines_rules/tines_story_jobs_clearance.py @@ -1,5 +1,4 @@ from global_filter_tines import filter_include_event -from panther_base_helpers import deep_get from panther_tines_helpers import tines_alert_context @@ -7,13 +6,13 @@ def rule(event): if not filter_include_event(event): return False - return deep_get(event, "operation_name", default="") == "StoryJobsClearance" + return event.get("operation_name", "") == "StoryJobsClearance" def title(event): - operation = deep_get(event, "operation_name", default="") - user = deep_get(event, "user_email", default="") - tines_instance = deep_get(event, "p_source_label", default="") + operation = event.get("operation_name", "") + user = event.get("user_email", "") + tines_instance = event.get("p_source_label", "") return f"Tines: [{operation}] performed by [{user}] on [{tines_instance}]." diff --git a/rules/tines_rules/tines_team_destruction.py b/rules/tines_rules/tines_team_destruction.py index 7eb30e71e..f8098c9b0 100644 --- a/rules/tines_rules/tines_team_destruction.py +++ b/rules/tines_rules/tines_team_destruction.py @@ -1,5 +1,4 @@ from global_filter_tines import filter_include_event -from panther_base_helpers import deep_get from panther_tines_helpers import tines_alert_context @@ -7,13 +6,13 @@ def rule(event): if not filter_include_event(event): return False - return deep_get(event, "operation_name", default="") == "TeamDestruction" + return event.get("operation_name", "") == "TeamDestruction" def title(event): - operation = deep_get(event, "operation_name", default="") - user = deep_get(event, "user_email", default="") - tines_instance = deep_get(event, "p_source_label", default="") + operation = event.get("operation_name", "") + user = event.get("user_email", "") + tines_instance = event.get("p_source_label", "") return f"Tines [{operation}] performed by [{user}] on [{tines_instance}]." diff --git a/rules/tines_rules/tines_tenant_authtoken.py b/rules/tines_rules/tines_tenant_authtoken.py index 87606bd17..0a326d890 100644 --- a/rules/tines_rules/tines_tenant_authtoken.py +++ b/rules/tines_rules/tines_tenant_authtoken.py @@ -1,5 +1,4 @@ from global_filter_tines import filter_include_event -from panther_base_helpers import deep_get from panther_tines_helpers import tines_alert_context ACTIONS = [ @@ -14,30 +13,30 @@ def rule(event): if not filter_include_event(event): return False - action = deep_get(event, "operation_name", default="") - is_tenant_token = deep_get(event, "inputs", "inputs", "isServiceToken", default=False) + action = event.get("operation_name", "") + is_tenant_token = event.deep_get("inputs", "inputs", "isServiceToken", default=False) return all([action in ACTIONS, is_tenant_token]) def title(event): - action = deep_get(event, "operation_name", default="") + action = event.get("operation_name", "") return ( f"Tines: Tenant [{action}] " - f"by [{deep_get(event, 'user_email', default='')}]" + f"by [{event.deep_get('user_email', default='')}]" ) def alert_context(event): a_c = tines_alert_context(event) - a_c["token_name"] = deep_get(event, "inputs", "inputs", "name", default="") + a_c["token_name"] = event.deep_get("inputs", "inputs", "name", default="") return a_c def dedup(event): return ( - f"{deep_get(event, 'user_id', default='')}" + f"{event.deep_get('user_id', default='')}" "_" - f"{deep_get(event, 'operation_name', default='')}" + f"{event.deep_get('operation_name', default='')}" "_" - f"{deep_get(event, 'inputs', 'inputs', 'name', default='')}" + f"{event.deep_get('inputs', 'inputs', 'name', default='')}" ) diff --git a/templates/example_rule.py b/templates/example_rule.py index 8584fb385..efc28cfcd 100644 --- a/templates/example_rule.py +++ b/templates/example_rule.py @@ -1,4 +1,4 @@ -from panther_base_helpers import deep_get, pattern_match +from panther_base_helpers import pattern_match ## Required @@ -6,7 +6,7 @@ # The logic to determine if an alert should send. # return True = Alert, False = Do not Alert def rule(event): - return event.get("field") == "value" and deep_get(event, "field", "nestedValue") + return event.get("field") == "value" and event.deep_get("field", "nestedValue") ## Optional Functions From 49f2deb98633e7853927c67ed3c090d13e308deb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 18:21:27 +0000 Subject: [PATCH 07/13] build(deps): bump actions/checkout from 4.1.7 to 4.2.1 (#1379) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-deprecated.yml | 2 +- .github/workflows/check-mitre.yml | 2 +- .github/workflows/check-packs.yml | 2 +- .github/workflows/docker.yml | 2 +- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/sync-from-upstream.yml | 2 +- .github/workflows/test.yml | 2 +- .github/workflows/upload.yml | 2 +- .github/workflows/validate.yml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/check-deprecated.yml b/.github/workflows/check-deprecated.yml index c34c2555e..33a7ea314 100644 --- a/.github/workflows/check-deprecated.yml +++ b/.github/workflows/check-deprecated.yml @@ -19,7 +19,7 @@ jobs: github.com:443 pypi.org:443 - name: Checkout panther-analysis - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 #v4.1.7 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1 - name: Fetch Release run: | diff --git a/.github/workflows/check-mitre.yml b/.github/workflows/check-mitre.yml index 4defbf3ef..5b7ce35ed 100644 --- a/.github/workflows/check-mitre.yml +++ b/.github/workflows/check-mitre.yml @@ -19,7 +19,7 @@ jobs: github.com:443 pypi.org:443 - name: Checkout panther-analysis - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1 - name: Set python version uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 #v5.2.0 diff --git a/.github/workflows/check-packs.yml b/.github/workflows/check-packs.yml index 6c6c6287d..d9138e2c0 100644 --- a/.github/workflows/check-packs.yml +++ b/.github/workflows/check-packs.yml @@ -22,7 +22,7 @@ jobs: pypi.org:443 - name: Checkout panther-analysis - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1 - name: Set python version uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 #v5.2.0 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 21ee22919..5abf3147e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -27,7 +27,7 @@ jobs: registry-1.docker.io:443 www.python.org:443 - name: Checkout panther-analysis - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1 - uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf #v3.2.0 - name: Set up Docker Buildx id: buildx diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ed5cf8694..4bf09a8c3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -19,7 +19,7 @@ jobs: github.com:443 pypi.org:443 - name: Checkout panther-analysis - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1 - name: Set python version uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 #v5.2.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d48783412..f0a2db651 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: - uses: step-security/harden-runner@91182cccc01eb5e619899d80e4e971d6181294a7 # v2.10.1 with: egress-policy: audit - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1 with: fetch-depth: 0 token: ${{ env.GITHUB_TOKEN }} diff --git a/.github/workflows/sync-from-upstream.yml b/.github/workflows/sync-from-upstream.yml index b43959537..b4fac6690 100644 --- a/.github/workflows/sync-from-upstream.yml +++ b/.github/workflows/sync-from-upstream.yml @@ -37,7 +37,7 @@ jobs: branch: "sync_upstream_${{steps.set_upstream.outputs.latest-release}}" # Checkout this repo into the branch - name: Checkout your local repo in PR branch - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1 with: ref: "sync_upstream_${{steps.set_upstream.outputs.latest-release}}" token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7e3033c29..5f2515f60 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,7 +24,7 @@ jobs: exit 0 - name: Checkout panther-analysis - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1 - name: Set python version uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 #v5.2.0 diff --git a/.github/workflows/upload.yml b/.github/workflows/upload.yml index f603d1cae..5bef43d8f 100644 --- a/.github/workflows/upload.yml +++ b/.github/workflows/upload.yml @@ -25,7 +25,7 @@ jobs: exit 0 - name: Checkout panther-analysis - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1 - name: Set python version uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 #v5.2.0 diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 080677b3c..65b4285cf 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -24,7 +24,7 @@ jobs: exit 0 - name: Checkout panther-analysis - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 #v4.2.0 + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1 - name: Set python version uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 #v5.2.0 From 67fa051ac5232448a766c2567418fc8008943aed Mon Sep 17 00:00:00 2001 From: Panos Sakkos Date: Thu, 10 Oct 2024 13:33:22 +0300 Subject: [PATCH 08/13] Fix linter error in gsuite_workspace_calendar_external_sharing.py (#1383) --- .github/workflows/check-deprecated.yml | 8 ++++---- .scripts/deleted_rules.py | 15 ++++++--------- .../gsuite_workspace_calendar_external_sharing.py | 4 ++-- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/.github/workflows/check-deprecated.yml b/.github/workflows/check-deprecated.yml index 33a7ea314..ec1f2e6c9 100644 --- a/.github/workflows/check-deprecated.yml +++ b/.github/workflows/check-deprecated.yml @@ -2,7 +2,7 @@ on: pull_request: permissions: - contents: read + contents: read jobs: check_removed_rules: @@ -20,10 +20,10 @@ jobs: pypi.org:443 - name: Checkout panther-analysis uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 #v4.2.1 - + - name: Fetch Release run: | - git fetch --depth=1 origin release + git fetch --depth=1 origin develop - name: Set python version uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 #v5.2.0 @@ -39,4 +39,4 @@ jobs: - name: Check for Removed Rules run: | pipenv run make check-deprecated - \ No newline at end of file + diff --git a/.scripts/deleted_rules.py b/.scripts/deleted_rules.py index 0edabe635..c7c4d2cc7 100644 --- a/.scripts/deleted_rules.py +++ b/.scripts/deleted_rules.py @@ -15,21 +15,21 @@ def get_deleted_ids() -> set[str]: # Run git diff, get output - result = subprocess.run(['git', 'diff', 'origin/release', 'HEAD'], capture_output=True) + result = subprocess.run(["git", "diff", "origin/develop", "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`. """ + """Returns all the IDs listed in `deprecated.txt`.""" with open("deprecated.txt", "r") as f: return set(f.read().split("\n")) @@ -43,6 +43,7 @@ def check(_): 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") @@ -61,11 +62,7 @@ def remove(args): ids = list(get_deprecated_ids()) pat_args = argparse.Namespace( - analysis_id = ids, - query_id = [], - confirm_bypass = True, - api_token = api_token, - api_host = api_host + analysis_id=ids, query_id=[], confirm_bypass=True, api_token=api_token, api_host=api_host ) logging.basicConfig( diff --git a/rules/gsuite_activityevent_rules/gsuite_workspace_calendar_external_sharing.py b/rules/gsuite_activityevent_rules/gsuite_workspace_calendar_external_sharing.py index 1a9c7b97f..d290a1d30 100644 --- a/rules/gsuite_activityevent_rules/gsuite_workspace_calendar_external_sharing.py +++ b/rules/gsuite_activityevent_rules/gsuite_workspace_calendar_external_sharing.py @@ -17,6 +17,6 @@ def title(event): return ( f"GSuite workspace setting for default calendar sharing was changed by " f"[{event.deep_get('actor', 'email', default='')}] " - f"from [{event.deep_get('parameters', 'OLD_VALUE', default='')}] " - f"to [{event.deep_get('parameters', 'NEW_VALUE', default='')}]" + + f"from [{event.deep_get('parameters', 'OLD_VALUE', default='')}] " + + "to [{event.deep_get('parameters', 'NEW_VALUE', default='')}]" ) From 9f4366a299f013549176fd8efe7d7051e8d80754 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 01:53:10 +0000 Subject: [PATCH 09/13] build(deps): bump thollander/actions-comment-pull-request from 2.5.0 to 3.0.0 (#1385) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-packs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-packs.yml b/.github/workflows/check-packs.yml index d9138e2c0..3c3bc664d 100644 --- a/.github/workflows/check-packs.yml +++ b/.github/workflows/check-packs.yml @@ -42,7 +42,7 @@ jobs: panther_analysis_tool check-packs || echo "errors=`cat errors.txt`" >> $GITHUB_OUTPUT - name: Comment PR - uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 + uses: thollander/actions-comment-pull-request@e2c37e53a7d2227b61585343765f73a9ca57eda9 if: ${{ steps.check-packs.outputs.errors }} with: mode: upsert @@ -54,7 +54,7 @@ jobs: ``` comment_tag: check-packs - name: Delete comment - uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 + uses: thollander/actions-comment-pull-request@e2c37e53a7d2227b61585343765f73a9ca57eda9 if: ${{ !steps.check-packs.outputs.errors }} with: mode: delete From 139d4054c1d346fe208beab1508f194845958273 Mon Sep 17 00:00:00 2001 From: ben-githubs <38414634+ben-githubs@users.noreply.github.com> Date: Mon, 14 Oct 2024 20:58:09 -0500 Subject: [PATCH 10/13] Configure `lint-mitre` to ignore schema test files (#1381) Co-authored-by: Ariel Ropek <79653153+arielkr256@users.noreply.github.com> --- .scripts/mitre_mapping_check.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.scripts/mitre_mapping_check.py b/.scripts/mitre_mapping_check.py index 0f5d0e91c..f7dd52d0d 100644 --- a/.scripts/mitre_mapping_check.py +++ b/.scripts/mitre_mapping_check.py @@ -13,8 +13,13 @@ def main(path: Path) -> bool: + # Ignore any schema test files + # Schema tests can't be loaded by panther_analysis_tool because each file contains multiple + # YAML documents. + ignore_files = list(path.glob("**/*_tests.y*ml")) + # Load Repo - analysis_items = load_analysis_specs([path], ignore_files=[]) + analysis_items = load_analysis_specs([path], ignore_files=ignore_files) items_with_invalid_mappings = [] # Record all items with bad tags for analysis_item in analysis_items: From 8e7a6f6ba51087c4cea5662910b8da48787b015e Mon Sep 17 00:00:00 2001 From: ben-githubs <38414634+ben-githubs@users.noreply.github.com> Date: Mon, 14 Oct 2024 22:27:37 -0500 Subject: [PATCH 11/13] Delete 'Snowflake.PublicRoleGrant' & query (#1386) --- deprecated.txt | 4 ++- packs/snowflake.yml | 2 -- .../snowflake_public_role_grant.py | 2 -- .../snowflake_public_role_grant.yml | 16 ---------- .../snowflake_public_role_grant_query.yml | 29 ------------------- 5 files changed, 3 insertions(+), 50 deletions(-) delete mode 100644 queries/snowflake_queries/snowflake_public_role_grant.py delete mode 100644 queries/snowflake_queries/snowflake_public_role_grant.yml delete mode 100644 queries/snowflake_queries/snowflake_public_role_grant_query.yml diff --git a/deprecated.txt b/deprecated.txt index 95642d3a0..b6bccd4ea 100644 --- a/deprecated.txt +++ b/deprecated.txt @@ -31,4 +31,6 @@ AWS.CloudTrail.RootFailedConsoleLogin AWS.S3.GreyNoiseActivity AWS.CloudTrail.RootConsoleLogin Okta.GeographicallyImprobableAccess -Okta.BruteForceLogins \ No newline at end of file +Okta.BruteForceLogins +Query.Snowflake.PublicRoleGrant +Snowflake.PublicRoleGrant \ No newline at end of file diff --git a/packs/snowflake.yml b/packs/snowflake.yml index c8a0d74fc..d1adb37d4 100644 --- a/packs/snowflake.yml +++ b/packs/snowflake.yml @@ -20,7 +20,6 @@ PackDefinition: - Query.Snowflake.KeyUserPasswordLogin - Query.Snowflake.MFALogin - Query.Snowflake.Multiple.Logins.Followed.By.Success - - Query.Snowflake.PublicRoleGrant - Query.Snowflake.SuspectedUserAccess - Query.Snowflake.TempStageCreated - Query.Snowflake.UserCreated @@ -38,7 +37,6 @@ PackDefinition: - Snowflake.KeyUserPasswordLogin - Snowflake.LoginWithoutMFA - Snowflake.Multiple.Failed.Logins.Followed.By.Success - - Snowflake.PublicRoleGrant - Snowflake.TempStageCreated - Snowflake.User.Access - Snowflake.UserCreated diff --git a/queries/snowflake_queries/snowflake_public_role_grant.py b/queries/snowflake_queries/snowflake_public_role_grant.py deleted file mode 100644 index 7a0c3657f..000000000 --- a/queries/snowflake_queries/snowflake_public_role_grant.py +++ /dev/null @@ -1,2 +0,0 @@ -def rule(_): - return True diff --git a/queries/snowflake_queries/snowflake_public_role_grant.yml b/queries/snowflake_queries/snowflake_public_role_grant.yml deleted file mode 100644 index 384ee50bf..000000000 --- a/queries/snowflake_queries/snowflake_public_role_grant.yml +++ /dev/null @@ -1,16 +0,0 @@ -AnalysisType: scheduled_rule -Filename: snowflake_public_role_grant.py -RuleID: "Snowflake.PublicRoleGrant" -Description: > - Detect additional grants to the public role -DisplayName: "Snowflake Grant to Public Role" -Enabled: false -Runbook: Determine if this is a necessary grant for the public role, which should be kept to the fewest possible. -ScheduledQueries: - - Query.Snowflake.PublicRoleGrant -Severity: Medium -Tests: - - Name: Value Returned By Query - ExpectedResult: true - Log: - Anything: any value diff --git a/queries/snowflake_queries/snowflake_public_role_grant_query.yml b/queries/snowflake_queries/snowflake_public_role_grant_query.yml deleted file mode 100644 index 7fcc20332..000000000 --- a/queries/snowflake_queries/snowflake_public_role_grant_query.yml +++ /dev/null @@ -1,29 +0,0 @@ -AnalysisType: scheduled_query -QueryName: "Query.Snowflake.PublicRoleGrant" -Enabled: false -Description: > - Monitor and detect alterations or grants to public role, which should be kept at a minimum. -AthenaQuery: | - /* athena query not supported */ - SELECT count(1) -SnowflakeQuery: | - --return instances of grants to the public role - - --this was adapted from a Security Feature Checklist query - - SELECT - user_name, - role_name, - query_text, - start_time as p_event_time, - end_time - FROM snowflake.account_usage.query_history - WHERE - p_occurs_since('1 day') - AND execution_status = 'SUCCESS' - AND query_type = 'GRANT' - AND query_text ILIKE '%to%public%' - ORDER BY end_time desc -Schedule: - RateMinutes: 1440 - TimeoutMinutes: 3 From b8fbbbd5dff37eb1d34889a3206e7d4c54a07503 Mon Sep 17 00:00:00 2001 From: ben-githubs <38414634+ben-githubs@users.noreply.github.com> Date: Mon, 14 Oct 2024 22:33:12 -0500 Subject: [PATCH 12/13] new rule: GCP.User.Added.To.Privileged.Group (#1378) Co-authored-by: Ariel Co-authored-by: Ariel Ropek <79653153+arielkr256@users.noreply.github.com> --- .../gcp_user_added_to_privileged_group.py | 35 +++++ .../gcp_user_added_to_privileged_group.yml | 140 ++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 rules/gcp_audit_rules/gcp_user_added_to_privileged_group.py create mode 100644 rules/gcp_audit_rules/gcp_user_added_to_privileged_group.yml diff --git a/rules/gcp_audit_rules/gcp_user_added_to_privileged_group.py b/rules/gcp_audit_rules/gcp_user_added_to_privileged_group.py new file mode 100644 index 000000000..e72b4717b --- /dev/null +++ b/rules/gcp_audit_rules/gcp_user_added_to_privileged_group.py @@ -0,0 +1,35 @@ +from panther_base_helpers import key_value_list_to_dict + +PRIVILEGED_GROUPS = { + # "admins@example.com" +} + +USER_EMAIL = "" +GROUP_EMAIL = "" + + +def rule(event): + events = event.deep_get("protoPayload", "metadata", "event", default=[]) + + for event_ in events: + if event_.get("eventname") != "ADD_GROUP_MEMBER": + continue + # Get the username + params = key_value_list_to_dict(event_.get("parameter", []), "name", "value") + global USER_EMAIL, GROUP_EMAIL # pylint: disable=global-statement + USER_EMAIL = params.get("USER_EMAIL") + GROUP_EMAIL = params.get("GROUP_EMAIL") + if GROUP_EMAIL in get_privileged_groups(): + return True + return False + + +def title(event): + actor = event.deep_get("actor", "email", default="") + global USER_EMAIL, GROUP_EMAIL + return f"{actor} has added {USER_EMAIL} to the privileged group {GROUP_EMAIL}" + + +def get_privileged_groups(): + # We make this a function, so we can mock it for unit tests + return PRIVILEGED_GROUPS diff --git a/rules/gcp_audit_rules/gcp_user_added_to_privileged_group.yml b/rules/gcp_audit_rules/gcp_user_added_to_privileged_group.yml new file mode 100644 index 000000000..982ab9e06 --- /dev/null +++ b/rules/gcp_audit_rules/gcp_user_added_to_privileged_group.yml @@ -0,0 +1,140 @@ +AnalysisType: rule +Filename: gcp_user_added_to_privileged_group.py +RuleID: "GCP.User.Added.To.Privileged.Group" +DisplayName: "GCP User Added to Privileged Group" +Enabled: false +LogTypes: + - GCP.AuditLog +Severity: Low +Tags: + - Configuration Required +Reports: + MITRE ATT&CK: + - TA0004:T1078.004 # Privilege Escalation: Valid Accounts: Cloud Accounts + - TA0004:T1484.001 # Privilege Escalation: Domain or Tenant Policy Modification: Group Policy Modification +Description: A user was added to a group with special previleges +DedupPeriodMinutes: 60 +Threshold: 1 +Reference: + https://github.com/GoogleCloudPlatform/security-analytics/blob/main/src/2.02/2.02.md +Runbook: Determine if the user had been added to the group for legitimate reasons. +Tests: + - Name: User Added to Privileged Group + ExpectedResult: true + Mocks: + - objectName: get_privileged_groups + returnValue: '["admins@example.com"]' + Log: + { + "logName": "organizations/123/logs/cloudaudit.googleapis.com%2Factivity", + "severity": "NOTICE", + "insertId": "285djodxlmu", + "resource": { + "type": "audited_resource", + "labels": { + "method": "google.admin.AdminService.addGroupMember", + "service": "admin.googleapis.com" + } + }, + "timestamp": "2022-03-22T22:12:58.916Z", + "receiveTimestamp": "2022-03-22T22:12:59.439766009Z", + "protoPayload": { + "@type": "type.googleapis.com/google.cloud.audit.AuditLog", + "serviceName": "admin.googleapis.com", + "methodName": "google.admin.AdminService.addGroupMember", + "resourceName": "organizations/123/groupSettings", + "authenticationInfo": { + "principalEmail": "admin@example.com" + }, + "requestMetadata": { + "callerIP": "11.22.33.44", + "requestAttributes": {}, + "destinationAttributes": {} + }, + "metadata": { + "@type": "type.googleapis.com/ccc_hosted_reporting.ActivityProto", + "activityId": { + "timeUsec": "1647987178916000", + "uniqQualifier": "-8614641986436885296" + }, + "event": [ + { + "eventName": "ADD_GROUP_MEMBER", + "eventType": "GROUP_SETTINGS", + "parameter": [ + { + "label": "LABEL_OPTIONAL", + "value": "test-user@example.com", + "type": "TYPE_STRING", + "name": "USER_EMAIL" + }, + { + "type": "TYPE_STRING", + "value": "admins@example.com", + "label": "LABEL_OPTIONAL", + "name": "GROUP_EMAIL" + } + ] + } + ] + } + } + } + - Name: User Added to Non-Privileged Group + ExpectedResult: false + Log: + { + "logName": "organizations/123/logs/cloudaudit.googleapis.com%2Factivity", + "severity": "NOTICE", + "insertId": "285djodxlmu", + "resource": { + "type": "audited_resource", + "labels": { + "method": "google.admin.AdminService.addGroupMember", + "service": "admin.googleapis.com" + } + }, + "timestamp": "2022-03-22T22:12:58.916Z", + "receiveTimestamp": "2022-03-22T22:12:59.439766009Z", + "protoPayload": { + "@type": "type.googleapis.com/google.cloud.audit.AuditLog", + "serviceName": "admin.googleapis.com", + "methodName": "google.admin.AdminService.addGroupMember", + "resourceName": "organizations/123/groupSettings", + "authenticationInfo": { + "principalEmail": "admin@example.com" + }, + "requestMetadata": { + "callerIP": "11.22.33.44", + "requestAttributes": {}, + "destinationAttributes": {} + }, + "metadata": { + "@type": "type.googleapis.com/ccc_hosted_reporting.ActivityProto", + "activityId": { + "timeUsec": "1647987178916000", + "uniqQualifier": "-8614641986436885296" + }, + "event": [ + { + "eventName": "ADD_GROUP_MEMBER", + "eventType": "GROUP_SETTINGS", + "parameter": [ + { + "label": "LABEL_OPTIONAL", + "value": "test-user@example.com", + "type": "TYPE_STRING", + "name": "USER_EMAIL" + }, + { + "type": "TYPE_STRING", + "value": "normies@example.com", + "label": "LABEL_OPTIONAL", + "name": "GROUP_EMAIL" + } + ] + } + ] + } + } + } From 32305a22304186508fa5b1106f98b62559cbd639 Mon Sep 17 00:00:00 2001 From: geoffg-sentry <165922362+geoffg-sentry@users.noreply.github.com> Date: Tue, 15 Oct 2024 10:35:43 -0400 Subject: [PATCH 13/13] Add AlertTitle to rule_jsonschema.json (#1384) Co-authored-by: Ariel Ropek <79653153+arielkr256@users.noreply.github.com> --- .vscode/rule_jsonschema.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.vscode/rule_jsonschema.json b/.vscode/rule_jsonschema.json index a63263c8c..f8309f307 100644 --- a/.vscode/rule_jsonschema.json +++ b/.vscode/rule_jsonschema.json @@ -5,6 +5,9 @@ "type": "object", "properties": { + "AlertTitle": { + "$ref": "#/definitions/AlertTitle" + }, "AnalysisType": { "$ref": "#/definitions/AnalysisType" }, @@ -85,6 +88,12 @@ } ], "definitions": { + "AlertTitle": { + "$comment": "https://docs.panther.com/detections/rules/writing-simple-detections#alerttitle", + "description": "Use AlertTitle to dynamically set the title of an alert generated by a match on this detection.", + "type": "string", + "default": "rule" + }, "AnalysisType": { "description": "what kind of detection", "type": "string",