diff --git a/global_helpers/panther_base_helpers.py b/global_helpers/panther_base_helpers.py index a32ac84b5..45f3b0b0c 100644 --- a/global_helpers/panther_base_helpers.py +++ b/global_helpers/panther_base_helpers.py @@ -58,3 +58,49 @@ def is_dmz_tags(resource): # function being used, etc. IN_PCI_SCOPE = in_pci_scope_tags IS_DMZ = is_dmz_tags + +GSUITE_PARAMETER_VALUES = [ + 'value', + 'intValue', + 'boolValue', + 'multiValue', + 'multiIntValue', + 'messageValue', + 'multiMessageValue', +] + + +# GSuite parameters are formatted as a list of dictionaries, where each dictionary has a 'name' key +# that maps to the name of the parameter, and one key from GSUITE_PARAMETER_VALUES that maps to the +# value of the parameter. This means to lookup the value of a particular parameter, you must +# traverse the entire list of parameters to find it and then know (or guess) what type of value it +# contains. This helper function handles that for us. +# +# Example parameters list: +# parameters = [ +# { +# "name": "event_id", +# "value": "abc123" +# }, +# { +# "name": "start_time", +# "intValue": 63731901000 +# }, +# { +# "name": "end_time", +# "intValue": 63731903000 +# }, +# { +# "name": "things", +# "multiValue": [ "DRIVE" , "MEME"] +# } +# ] +def gsuite_parameter_lookup(parameters, key): + for param in parameters: + if param['name'] != key: + continue + for value in GSUITE_PARAMETER_VALUES: + if value in param: + return param[value] + return None + return None diff --git a/gsuite_reports_rules/gsuite_advanced_protection.py b/gsuite_reports_rules/gsuite_advanced_protection.py new file mode 100644 index 000000000..a20ff7f20 --- /dev/null +++ b/gsuite_reports_rules/gsuite_advanced_protection.py @@ -0,0 +1,19 @@ +def rule(event): + if event['id'].get('applicationName') != 'user_accounts': + return False + + for details in event.get('events', [{}]): + if (details.get('type') == 'titanium_change' and + details.get('name') == 'titanium_unenroll'): + return True + + return False + + +def dedup(event): + return event.get('actor', {}).get('email') + + +def title(event): + return 'Advanced protection was disabled for user [{}]'.format( + event.get('actor', {}).get('email')) diff --git a/gsuite_reports_rules/gsuite_advanced_protection.yml b/gsuite_reports_rules/gsuite_advanced_protection.yml new file mode 100644 index 000000000..a89a5a756 --- /dev/null +++ b/gsuite_reports_rules/gsuite_advanced_protection.yml @@ -0,0 +1,46 @@ +AnalysisType: rule +Filename: gsuite_advanced_protection.py +RuleID: GSuite.AdvancedProtection +DisplayName: GSuite User Advanced Protection Change +Enabled: true +LogTypes: + - GSuite.Reports +Tags: + - GSuite +Severity: Low +Description: > + A user disabled advanced protection for themselves. +Reference: https://developers.google.com/admin-sdk/reports/v1/appendix/activity/user-accounts#titanium_change +Runbook: > + Have the user re-enable Google Advanced Protection +Tests: + - + Name: Advanced Protection Enabled + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'user_accounts'}, + 'events': [ + { + 'type': 'titanium_change', + 'name': 'titanium_enroll' + } + ] + } + - + Name: Advanced Protection Disabled + LogType: GSuites.Reports + ExpectedResult: true + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'user_accounts'}, + 'events': [ + { + 'type': 'titanium_change', + 'name': 'titanium_unenroll' + } + ] + } diff --git a/gsuite_reports_rules/gsuite_doc_ownership_transfer.py b/gsuite_reports_rules/gsuite_doc_ownership_transfer.py new file mode 100644 index 000000000..fa0fea570 --- /dev/null +++ b/gsuite_reports_rules/gsuite_doc_ownership_transfer.py @@ -0,0 +1,19 @@ +from panther_base_helpers import gsuite_parameter_lookup as param_lookup + +ORG_DOMAINS = { + '@example.com', +} + + +def rule(event): + if event['id'].get('applicationName') != 'admin': + return False + + for details in event.get('events', [{}]): + if (details.get('type') == 'DOCS_SETTINGS' and + details.get('name') == 'TRANSFER_DOCUMENT_OWNERSHIP'): + new_owner = param_lookup(details.get('parameters', {}), 'NEW_VALUE') + return bool(new_owner) and not any( + new_owner.endswith(x) for x in ORG_DOMAINS) + + return False diff --git a/gsuite_reports_rules/gsuite_doc_ownership_transfer.yml b/gsuite_reports_rules/gsuite_doc_ownership_transfer.yml new file mode 100644 index 000000000..1aaf7000b --- /dev/null +++ b/gsuite_reports_rules/gsuite_doc_ownership_transfer.yml @@ -0,0 +1,49 @@ +AnalysisType: rule +Filename: gsuite_doc_ownership_transfer.py +RuleID: GSuite.DocOwnershipTransfer +DisplayName: GSuite Document External Ownership Transfer +Enabled: false +LogTypes: + - GSuite.Reports +Tags: + - GSuite + - Configuration Required +Severity: Low +Description: > + A GSuite document's ownership was transferred to an external party. +Reference: https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-docs-settings#TRANSFER_DOCUMENT_OWNERSHIP +Runbook: > + Verify that this document did not contain sensitive or private company information. +Tests: + - + Name: Ownership Transferred Within Organization + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'admin'}, + 'events': [ + { + 'type': 'DOCS_SETTINGS', + 'name': 'TRANSFER_DOCUMENT_OWNERSHIP', + 'parameters': [{'name': 'NEW_VALUE', 'value': 'user@example.com'}] + } + ] + } + - + Name: Document Transferred to External User + LogType: GSuites.Reports + ExpectedResult: true + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'admin'}, + 'events': [ + { + 'type': 'DOCS_SETTINGS', + 'name': 'TRANSFER_DOCUMENT_OWNERSHIP', + 'parameters': [{'name': 'NEW_VALUE', 'value': 'user@badguy.com'}] + } + ] + } diff --git a/gsuite_reports_rules/gsuite_drive_overly_visible.py b/gsuite_reports_rules/gsuite_drive_overly_visible.py new file mode 100644 index 000000000..4d037fd60 --- /dev/null +++ b/gsuite_reports_rules/gsuite_drive_overly_visible.py @@ -0,0 +1,36 @@ +from panther_base_helpers import gsuite_parameter_lookup as param_lookup + +RESOURCE_CHANGE_EVENTS = { + 'create', + 'move', + 'upload', + 'edit', +} + +PERMISSIVE_VISIBILITY = { + 'people_with_link', + 'public_on_the_web', +} + + +def rule(event): + if event['id'].get('applicationName') != 'drive': + return False + + for details in event.get('events', [{}]): + if (details.get('type') == 'access' and + details.get('name') in RESOURCE_CHANGE_EVENTS and + param_lookup(details.get('parameters', {}), + 'visibility') in PERMISSIVE_VISIBILITY): + return True + + return False + + +def dedup(event): + return event['p_row_id'] + + +def title(event): + return 'User [{}] modified a document that has overly permissive share settings'.format( + event.get('actor', {}).get('email')) diff --git a/gsuite_reports_rules/gsuite_drive_overly_visible.yml b/gsuite_reports_rules/gsuite_drive_overly_visible.yml new file mode 100644 index 000000000..8bdc2f527 --- /dev/null +++ b/gsuite_reports_rules/gsuite_drive_overly_visible.yml @@ -0,0 +1,63 @@ +AnalysisType: rule +Filename: gsuite_drive_overly_visible.py +RuleID: GSuite.DriveOverlyVisible +DisplayName: GSuite Overly Visible Drive Document +Enabled: true +LogTypes: + - GSuite.Reports +Tags: + - GSuite +Severity: Medium +Description: > + A Google drive resource that is overly visible has been modified. +Reference: https://developers.google.com/admin-sdk/reports/v1/appendix/activity/drive#access +Runbook: > + Investigate whether the drive document is appropriate to be this visible. +Tests: + - + Name: Access Event + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'drive'}, + 'events': [ + { + 'type': 'access', + 'name': 'download' + } + ] + } + - + Name: Modify Event Without Over Visibility + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'drive'}, + 'events': [ + { + 'type': 'access', + 'name': 'edit', + 'parameters': [{'name': 'visibility', 'value': 'private'}] + } + ] + } + - + Name: Overly Visible Doc Modified + LogType: GSuites.Reports + ExpectedResult: true + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'drive'}, + 'events': [ + { + 'type': 'access', + 'name': 'edit', + 'parameters': [{'name': 'visibility', 'value': 'people_with_link'}] + } + ] + } diff --git a/gsuite_reports_rules/gsuite_drive_visibility_change.py b/gsuite_reports_rules/gsuite_drive_visibility_change.py new file mode 100644 index 000000000..5f82dec32 --- /dev/null +++ b/gsuite_reports_rules/gsuite_drive_visibility_change.py @@ -0,0 +1,23 @@ +from panther_base_helpers import gsuite_parameter_lookup as param_lookup + + +def rule(event): + if event['id'].get('applicationName') != 'drive': + return False + + for details in event.get('events', [{}]): + if (details.get('type') == 'acl_change' and + param_lookup(details.get('parameters', {}), + 'visibility_change') == 'external'): + return True + + return False + + +def dedup(event): + return event['p_row_id'] + + +def title(event): + return 'User [{}] made a document externally visible for the first time'.format( + event.get('actor', {}).get('email')) diff --git a/gsuite_reports_rules/gsuite_drive_visibility_change.yml b/gsuite_reports_rules/gsuite_drive_visibility_change.yml new file mode 100644 index 000000000..6566ae441 --- /dev/null +++ b/gsuite_reports_rules/gsuite_drive_visibility_change.yml @@ -0,0 +1,76 @@ +AnalysisType: rule +Filename: gsuite_drive_visibility_change.py +RuleID: GSuite.DriveVisiblityChanged +DisplayName: GSuite External Drive Document +Enabled: true +LogTypes: + - GSuite.Reports +Tags: + - GSuite +Severity: Medium +Description: > + A Google drive resource became externally accessible. +Reference: https://developers.google.com/admin-sdk/reports/v1/appendix/activity/drive#acl_change +Runbook: > + Investigate whether the drive document is appropriate to be publicly accessible. +Tests: + - + Name: Access Event + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'drive'}, + 'events': [ + { + 'type': 'access', + 'name': 'upload' + } + ] + } + - + Name: ACL Change without Visiblity Change + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'drive'}, + 'events': [ + { + 'type': 'acl_change', + 'name': 'shared_drive_settings_change' + } + ] + } + - + Name: Doc Became Public + LogType: GSuites.Reports + ExpectedResult: true + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'drive'}, + 'events': [ + { + 'type': 'acl_change', + 'parameters': [{'name': 'visibility_change', 'value': 'external'}] + } + ] + } + - + Name: Doc Became Private + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'drive'}, + 'events': [ + { + 'type': 'acl_change', + 'parameters': [{'name': 'visibility_change', 'value': 'internal'}] + } + ] + } diff --git a/gsuite_reports_rules/gsuite_google_access.py b/gsuite_reports_rules/gsuite_google_access.py new file mode 100644 index 000000000..4afcb97d3 --- /dev/null +++ b/gsuite_reports_rules/gsuite_google_access.py @@ -0,0 +1,10 @@ +def rule(event): + if event['id'].get('applicationName') != 'access_transparency': + return False + + for details in event.get('events', [{}]): + if (details.get('type') == 'GSUITE_RESOURCE' and + details.get('name') == 'ACCESS'): + return True + + return False diff --git a/gsuite_reports_rules/gsuite_google_access.yml b/gsuite_reports_rules/gsuite_google_access.yml new file mode 100644 index 000000000..69234b71a --- /dev/null +++ b/gsuite_reports_rules/gsuite_google_access.yml @@ -0,0 +1,40 @@ +AnalysisType: rule +Filename: gsuite_google_access.py +RuleID: GSuite.GoogleAccess +DisplayName: Google Accessed a GSuite Reource +Enabled: true +LogTypes: + - GSuite.Reports +Tags: + - GSuite +Severity: Low +Description: > + Google accessed one of your GSuite resources directly, most likely in response to a support incident. +Reference: https://developers.google.com/admin-sdk/reports/v1/appendix/activity/access-transparency +Runbook: > + Your GSuite Super Admin can visit the Access Transparency report in the GSuite Admin Dashboard to see more details about the access. +Tests: + - + Name: Normal Login Event + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'login'}, + 'events': [{'type': 'login'}] + } + - + Name: Resource Accessed by Google + LogType: GSuites.Reports + ExpectedResult: true + Log: + { + 'id': {'applicationName': 'access_transparency'}, + 'events': [ + { + 'type': 'GSUITE_RESOURCE', + 'name': 'ACCESS' + } + ] + } diff --git a/gsuite_reports_rules/gsuite_group_banned_user.py b/gsuite_reports_rules/gsuite_group_banned_user.py new file mode 100644 index 000000000..b3332d3d0 --- /dev/null +++ b/gsuite_reports_rules/gsuite_group_banned_user.py @@ -0,0 +1,19 @@ +def rule(event): + if event['id'].get('applicationName') != 'groups_enterprise': + return False + + for details in event.get('events', [{}]): + if (details.get('type') == 'moderator_action' and + details.get('name') == 'ban_user_with_moderation'): + return True + + return False + + +def dedup(event): + return event.get('actor', {}).get('email') + + +def title(event): + return 'User [{}] banned another user from a group.'.format( + event.get('actor', {}).get('email')) diff --git a/gsuite_reports_rules/gsuite_group_banned_user.yml b/gsuite_reports_rules/gsuite_group_banned_user.yml new file mode 100644 index 000000000..b304e6636 --- /dev/null +++ b/gsuite_reports_rules/gsuite_group_banned_user.yml @@ -0,0 +1,46 @@ +AnalysisType: rule +Filename: gsuite_group_banned_user.py +RuleID: GSuite.GroupBannedUser +DisplayName: GSuite User Banned from Group +Enabled: true +LogTypes: + - GSuite.Reports +Tags: + - GSuite +Severity: Low +Description: > + A GSuite user was banned from an enterprise group by moderator action. +Reference: https://developers.google.com/admin-sdk/reports/v1/appendix/activity/groups-enterprise#ban_user_with_moderation +Runbook: > + Investigate the banned user to see if further disciplinary action needs to be taken. +Tests: + - + Name: User Added + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'groups_enterprise'}, + 'events': [ + { + 'type': 'moderator_action', + 'name': 'add_user' + } + ] + } + - + Name: User Banned from Group + LogType: GSuites.Reports + ExpectedResult: true + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'groups_enterprise'}, + 'events': [ + { + 'type': 'moderator_action', + 'name': 'ban_user_with_moderation' + } + ] + } diff --git a/gsuite_reports_rules/gsuite_high_severity_rule.py b/gsuite_reports_rules/gsuite_high_severity_rule.py new file mode 100644 index 000000000..2e45c3cea --- /dev/null +++ b/gsuite_reports_rules/gsuite_high_severity_rule.py @@ -0,0 +1,14 @@ +from panther_base_helpers import gsuite_parameter_lookup as param_lookup + + +def rule(event): + if event['id'].get('applicationName') != 'rules': + return False + + for details in event.get('events', [{}]): + if (details.get('type') == 'rule_trigger_type' and + details.get('name') == 'rule_trigger' and param_lookup( + details.get('parameters', {}), 'severity') == 'HIGH'): + return True + + return False diff --git a/gsuite_reports_rules/gsuite_high_severity_rule.yml b/gsuite_reports_rules/gsuite_high_severity_rule.yml new file mode 100644 index 000000000..8aac65f0f --- /dev/null +++ b/gsuite_reports_rules/gsuite_high_severity_rule.yml @@ -0,0 +1,62 @@ +AnalysisType: rule +Filename: gsuite_high_severity_rule.py +RuleID: GSuite.HighSeverityRule +DisplayName: GSuite High Severity Rule Triggered +Enabled: true +LogTypes: + - GSuite.Reports +Tags: + - GSuite +Severity: High +Description: > + A high severity GSuite rule was triggered. +Reference: https://support.google.com/a/answer/9420866 +Runbook: > + Investigate what triggered the rule. +Tests: + - + Name: Non Triggered Rule + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'rules'}, + 'events': [ + { + 'type': 'action_complete_type', + } + ] + } + - + Name: High Severity Rule + LogType: GSuites.Reports + ExpectedResult: true + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'rules'}, + 'events': [ + { + 'type': 'rule_trigger_type', + 'name': 'rule_trigger', + 'parameters': [{'name': 'severity', 'value': 'HIGH'}] + } + ] + } + - + Name: Medium Severity Rule + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'rules'}, + 'events': [ + { + 'type': 'rule_trigger_type', + 'name': 'rule_trigger', + 'parameters': [{'name': 'severity', 'value': 'MEDIUM'}] + } + ] + } diff --git a/gsuite_reports_rules/gsuite_login_type.py b/gsuite_reports_rules/gsuite_login_type.py new file mode 100644 index 000000000..dd8a03269 --- /dev/null +++ b/gsuite_reports_rules/gsuite_login_type.py @@ -0,0 +1,33 @@ +from panther_base_helpers import gsuite_parameter_lookup as param_lookup + +# Remove any unapproved login methods +APPROVED_LOGIN_TYPES = { + 'exchange', + 'google_password', + 'reauth', + 'saml', + 'unknown', +} + + +def rule(event): + if event['id'].get('applicationName') != 'login': + return False + + for details in event.get('events', [{}]): + if (details.get('type') == 'login' and + details.get('name') != 'logout' and + param_lookup(details.get('parameters', {}), + 'login_type') not in APPROVED_LOGIN_TYPES): + return True + + return False + + +def dedup(event): + return event.get('actor', {}).get('email') + + +def title(event): + return 'A login attempt of a non-approved type was detected for user [{}]'.format( + event.get('actor', {}).get('email')) diff --git a/gsuite_reports_rules/gsuite_login_type.yml b/gsuite_reports_rules/gsuite_login_type.yml new file mode 100644 index 000000000..535cc815b --- /dev/null +++ b/gsuite_reports_rules/gsuite_login_type.yml @@ -0,0 +1,47 @@ +AnalysisType: rule +Filename: gsuite_login_type.py +RuleID: GSuite.LoginType +DisplayName: GSuite Login Type +Enabled: false +LogTypes: + - GSuite.Reports +Tags: + - GSuite + - Configuration Required +Severity: Medium +Description: > + A login of a non-approved type was detected for this user. +Reference: https://developers.google.com/admin-sdk/reports/v1/appendix/activity/login#login +Runbook: > + Correct the user account settings so that only logins of approved types are available. +Tests: + - + Name: Login With Approved Type + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'login'}, + 'events': [ + { + 'type': 'login', + 'parameters': [{'name': 'login_type', 'value': 'saml'}] + } + ] + } + - + Name: Unapproved Login Type + LogType: GSuites.Reports + ExpectedResult: true + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'login'}, + 'events': [ + { + 'type': 'login', + 'parameters': [{'name': 'login_type', 'value': 'carrier_pigeon'}] + } + ] + } diff --git a/gsuite_reports_rules/gsuite_low_severity_rule.py b/gsuite_reports_rules/gsuite_low_severity_rule.py new file mode 100644 index 000000000..81010bbe4 --- /dev/null +++ b/gsuite_reports_rules/gsuite_low_severity_rule.py @@ -0,0 +1,14 @@ +from panther_base_helpers import gsuite_parameter_lookup as param_lookup + + +def rule(event): + if event['id'].get('applicationName') != 'rules': + return False + + for details in event.get('events', [{}]): + if (details.get('type') == 'rule_trigger_type' and + details.get('name') == 'rule_trigger' and param_lookup( + details.get('parameters', {}), 'severity') == 'LOW'): + return True + + return False diff --git a/gsuite_reports_rules/gsuite_low_severity_rule.yml b/gsuite_reports_rules/gsuite_low_severity_rule.yml new file mode 100644 index 000000000..f5a989b72 --- /dev/null +++ b/gsuite_reports_rules/gsuite_low_severity_rule.yml @@ -0,0 +1,62 @@ +AnalysisType: rule +Filename: gsuite_low_severity_rule.py +RuleID: GSuite.LowSeverityRule +DisplayName: GSuite Low Severity Rule Triggered +Enabled: true +LogTypes: + - GSuite.Reports +Tags: + - GSuite +Severity: Low +Description: > + A low severity GSuite rule was triggered. +Reference: https://support.google.com/a/answer/9420866 +Runbook: > + Investigate what triggered the rule. +Tests: + - + Name: Non Triggered Rule + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'rules'}, + 'events': [ + { + 'type': 'action_complete_type', + } + ] + } + - + Name: Low Severity Rule + LogType: GSuites.Reports + ExpectedResult: true + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'rules'}, + 'events': [ + { + 'type': 'rule_trigger_type', + 'name': 'rule_trigger', + 'parameters': [{'name': 'severity', 'value': 'LOW'}] + } + ] + } + - + Name: Medium Severity Rule + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'rules'}, + 'events': [ + { + 'type': 'rule_trigger_type', + 'name': 'rule_trigger', + 'parameters': [{'name': 'severity', 'value': 'MEDIUM'}] + } + ] + } diff --git a/gsuite_reports_rules/gsuite_medium_severity_rule.py b/gsuite_reports_rules/gsuite_medium_severity_rule.py new file mode 100644 index 000000000..ab302211c --- /dev/null +++ b/gsuite_reports_rules/gsuite_medium_severity_rule.py @@ -0,0 +1,14 @@ +from panther_base_helpers import gsuite_parameter_lookup as param_lookup + + +def rule(event): + if event['id'].get('applicationName') != 'rules': + return False + + for details in event.get('events', [{}]): + if (details.get('type') == 'rule_trigger_type' and + details.get('name') == 'rule_trigger' and param_lookup( + details.get('parameters', {}), 'severity') == 'MEDIUM'): + return True + + return False diff --git a/gsuite_reports_rules/gsuite_medium_severity_rule.yml b/gsuite_reports_rules/gsuite_medium_severity_rule.yml new file mode 100644 index 000000000..4e7141c72 --- /dev/null +++ b/gsuite_reports_rules/gsuite_medium_severity_rule.yml @@ -0,0 +1,62 @@ +AnalysisType: rule +Filename: gsuite_medium_severity_rule.py +RuleID: GSuite.MediumSeverityRule +DisplayName: GSuite Medium Severity Rule Triggered +Enabled: true +LogTypes: + - GSuite.Reports +Tags: + - GSuite +Severity: Medium +Description: > + A medium severity GSuite rule was triggered. +Reference: https://support.google.com/a/answer/9420866 +Runbook: > + Investigate what triggered the rule. +Tests: + - + Name: Non Triggered Rule + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'rules'}, + 'events': [ + { + 'type': 'action_complete_type', + } + ] + } + - + Name: High Severity Rule + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'rules'}, + 'events': [ + { + 'type': 'rule_trigger_type', + 'name': 'rule_trigger', + 'parameters': [{'name': 'severity', 'value': 'HIGH'}] + } + ] + } + - + Name: Medium Severity Rule + LogType: GSuites.Reports + ExpectedResult: true + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'rules'}, + 'events': [ + { + 'type': 'rule_trigger_type', + 'name': 'rule_trigger', + 'parameters': [{'name': 'severity', 'value': 'MEDIUM'}] + } + ] + } diff --git a/gsuite_reports_rules/gsuite_mobile_device_compromise.py b/gsuite_reports_rules/gsuite_mobile_device_compromise.py new file mode 100644 index 000000000..1ec7badf1 --- /dev/null +++ b/gsuite_reports_rules/gsuite_mobile_device_compromise.py @@ -0,0 +1,24 @@ +from panther_base_helpers import gsuite_parameter_lookup as param_lookup + + +def rule(event): + if event['id'].get('applicationName') != 'mobile': + return False + + for details in event.get('events', [{}]): + if (details.get('type') == 'suspicious_activity' and + details.get('name') == 'DEVICE_COMPROMISED_EVENT' and + param_lookup(details.get('parameters', {}), + 'DEVICE_COMPROMISED_STATE') == 'COMPROMISED'): + return True + + return False + + +def dedup(event): + return event.get('actor', {}).get('email') + + +def title(event): + return 'User [{}]\'s device was compromised'.format( + event.get('actor', {}).get('email')) diff --git a/gsuite_reports_rules/gsuite_mobile_device_compromise.yml b/gsuite_reports_rules/gsuite_mobile_device_compromise.yml new file mode 100644 index 000000000..1bcebc8b6 --- /dev/null +++ b/gsuite_reports_rules/gsuite_mobile_device_compromise.yml @@ -0,0 +1,58 @@ +AnalysisType: rule +Filename: gsuite_mobile_device_compromise.py +RuleID: GSuite.DeviceCompromise +DisplayName: GSuite User Device Compromised +Enabled: true +LogTypes: + - GSuite.Reports +Tags: + - GSuite +Severity: Medium +Description: > + GSuite reported a user's device has been compromised. +Reference: https://developers.google.com/admin-sdk/reports/v1/appendix/activity/mobile#DEVICE_COMPROMISED_EVENT +Runbook: > + Have the user change their passwords and reset the device. +Tests: + - + Name: Normal Mobile Event + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'mobile'}, + 'events': [{'type': 'device_updates'}] + } + - + Name: Suspicious Activity Shows not Compromised + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'mobile'}, + 'events': [ + { + 'type': 'suspicious_activity', + 'name': 'DEVICE_COMPROMISED_EVENT', + 'parameters': [{'name': 'DEVICE_COMPROMISED_STATE', 'value': 'NOT_COMPROMISED'}] + } + ] + } + - + Name: Suspicious Activity Shows Compromised + LogType: GSuites.Reports + ExpectedResult: true + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'mobile'}, + 'events': [ + { + 'type': 'suspicious_activity', + 'name': 'DEVICE_COMPROMISED_EVENT', + 'parameters': [{'name': 'DEVICE_COMPROMISED_STATE', 'value': 'COMPROMISED'}] + } + ] + } diff --git a/gsuite_reports_rules/gsuite_mobile_device_screen_unlock_fail.py b/gsuite_reports_rules/gsuite_mobile_device_screen_unlock_fail.py new file mode 100644 index 000000000..3b6b58294 --- /dev/null +++ b/gsuite_reports_rules/gsuite_mobile_device_screen_unlock_fail.py @@ -0,0 +1,26 @@ +from panther_base_helpers import gsuite_parameter_lookup as param_lookup + +MAX_UNLOCK_ATTEMPTS = 10 + + +def rule(event): + if event['id'].get('applicationName') != 'mobile': + return False + + for details in event.get('events', [{}]): + if (details.get('type') == 'suspicious_activity' and + details.get('name') == 'FAILED_PASSWORD_ATTEMPTS_EVENT' and + param_lookup(details.get('parameters', {}), + 'FAILED_PASSWD_ATTEMPTS') > MAX_UNLOCK_ATTEMPTS): + return True + + return False + + +def dedup(event): + return event.get('actor', {}).get('email') + + +def title(event): + return 'User [{}]\'s device had multiple failed unlock attempts'.format( + event.get('actor', {}).get('email')) diff --git a/gsuite_reports_rules/gsuite_mobile_device_screen_unlock_fail.yml b/gsuite_reports_rules/gsuite_mobile_device_screen_unlock_fail.yml new file mode 100644 index 000000000..d99c61cff --- /dev/null +++ b/gsuite_reports_rules/gsuite_mobile_device_screen_unlock_fail.yml @@ -0,0 +1,58 @@ +AnalysisType: rule +Filename: gsuite_mobile_device_screen_unlock_fail.py +RuleID: GSuite.DeviceUnlockFailure +DisplayName: GSuite User Device Unlock Failures +Enabled: true +LogTypes: + - GSuite.Reports +Tags: + - GSuite +Severity: Medium +Description: > + Someone failed to unlock a user's device multiple times in quick succession. +Reference: https://developers.google.com/admin-sdk/reports/v1/appendix/activity/mobile#FAILED_PASSWORD_ATTEMPTS_EVENT +Runbook: > + Verify that these unlock attempts came from the user, and not a malicious actor which has acquired the user's device. +Tests: + - + Name: Normal Mobile Event + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'mobile'}, + 'events': [{'type': 'device_updates'}] + } + - + Name: Small Number of Failed Logins + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'mobile'}, + 'events': [ + { + 'type': 'suspicious_activity', + 'name': 'FAILED_PASSWORD_ATTEMPTS_EVENT', + 'parameters': [{'name': 'FAILED_PASSWD_ATTEMPTS', 'intValue': 1}] + } + ] + } + - + Name: Multiple Failed Login Attempts + LogType: GSuites.Reports + ExpectedResult: true + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'mobile'}, + 'events': [ + { + 'type': 'suspicious_activity', + 'name': 'FAILED_PASSWORD_ATTEMPTS_EVENT', + 'parameters': [{'name': 'FAILED_PASSWD_ATTEMPTS', 'intValue': 100}] + } + ] + } diff --git a/gsuite_reports_rules/gsuite_mobile_device_suspicious_activity.py b/gsuite_reports_rules/gsuite_mobile_device_suspicious_activity.py new file mode 100644 index 000000000..3e5eaf8d6 --- /dev/null +++ b/gsuite_reports_rules/gsuite_mobile_device_suspicious_activity.py @@ -0,0 +1,19 @@ +def rule(event): + if event['id'].get('applicationName') != 'mobile': + return False + + for details in event.get('events', [{}]): + if (details.get('type') == 'suspicious_activity' and + details.get('name') == 'SUSPICIOUS_ACTIVITY_EVENT'): + return True + + return False + + +def dedup(event): + return event.get('actor', {}).get('email') + + +def title(event): + return 'User [{}]\'s device was compromised'.format( + event.get('actor', {}).get('email')) diff --git a/gsuite_reports_rules/gsuite_mobile_device_suspicious_activity.yml b/gsuite_reports_rules/gsuite_mobile_device_suspicious_activity.yml new file mode 100644 index 000000000..19dbc4deb --- /dev/null +++ b/gsuite_reports_rules/gsuite_mobile_device_suspicious_activity.yml @@ -0,0 +1,41 @@ +AnalysisType: rule +Filename: gsuite_mobile_device_suspicious_activity.py +RuleID: GSuite.DeviceSuspiciousActivity +DisplayName: GSuite Device Suspicious Activity +Enabled: true +LogTypes: + - GSuite.Reports +Tags: + - GSuite +Severity: Low +Description: > + GSuite reported a suspicious activity on a user's device. +Reference: https://developers.google.com/admin-sdk/reports/v1/appendix/activity/mobile#SUSPICIOUS_ACTIVITY_EVENT +Runbook: > + Validate that the suspicious activity was expected by the user. +Tests: + - + Name: Normal Mobile Event + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'mobile'}, + 'events': [{'type': 'device_updates'}] + } + - + Name: Suspicious Activity + LogType: GSuites.Reports + ExpectedResult: true + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'mobile'}, + 'events': [ + { + 'type': 'suspicious_activity', + 'name': 'SUSPICIOUS_ACTIVITY_EVENT', + } + ] + } diff --git a/gsuite_reports_rules/gsuite_permissions_delegated.py b/gsuite_reports_rules/gsuite_permissions_delegated.py new file mode 100644 index 000000000..d9884430d --- /dev/null +++ b/gsuite_reports_rules/gsuite_permissions_delegated.py @@ -0,0 +1,19 @@ +def rule(event): + if event['id'].get('applicationName') != 'admin': + return False + + for details in event.get('events', [{}]): + if (details.get('type') == 'DELEGATED_ADMIN_SETTINGS' and + details.get('name') == 'ASSIGN_ROLE'): + return True + + return False + + +def dedup(event): + return event.get('actor', {}).get('email') + + +def title(event): + return 'User [{}] was delegated new administrator privileges'.format( + event.get('actor', {}).get('email')) diff --git a/gsuite_reports_rules/gsuite_permissions_delegated.yml b/gsuite_reports_rules/gsuite_permissions_delegated.yml new file mode 100644 index 000000000..c5b7b759e --- /dev/null +++ b/gsuite_reports_rules/gsuite_permissions_delegated.yml @@ -0,0 +1,46 @@ +AnalysisType: rule +Filename: gsuite_permissions_delegated.py +RuleID: GSuite.PermisssionsDelegated +DisplayName: GSuite User Delegated Admin Permissions +Enabled: true +LogTypes: + - GSuite.Reports +Tags: + - GSuite +Severity: Low +Description: > + A GSuite user was granted new admininstrator privileges. +Reference: https://developers.google.com/admin-sdk/reports/v1/appendix/activity/admin-delegated-admin-settings#ASSIGN_ROLE +Runbook: > + Valdiate that this users should have these permissions and they are not the result of a privilege escalation attack. +Tests: + - + Name: Other Admin Action + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'admin'}, + 'events': [ + { + 'type': 'DELEGATED_ADMIN_SETTINGS', + 'name': 'RENAME_ROLE' + } + ] + } + - + Name: Privileges Assigned + LogType: GSuites.Reports + ExpectedResult: true + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'admin'}, + 'events': [ + { + 'type': 'DELEGATED_ADMIN_SETTINGS', + 'name': 'ASSIGN_ROLE' + } + ] + } diff --git a/gsuite_reports_rules/gsuite_two_step_verification.py b/gsuite_reports_rules/gsuite_two_step_verification.py new file mode 100644 index 000000000..02419b66a --- /dev/null +++ b/gsuite_reports_rules/gsuite_two_step_verification.py @@ -0,0 +1,19 @@ +def rule(event): + if event['id'].get('applicationName') != 'user_accounts': + return False + + for details in event.get('events', [{}]): + if (details.get('type') == '2sv_change' and + details.get('name') == '2sv_disable'): + return True + + return False + + +def dedup(event): + return event.get('actor', {}).get('email') + + +def title(event): + return 'Two step verification was disabled for user [{}]'.format( + event.get('actor', {}).get('email')) diff --git a/gsuite_reports_rules/gsuite_two_step_verification.yml b/gsuite_reports_rules/gsuite_two_step_verification.yml new file mode 100644 index 000000000..915ed2008 --- /dev/null +++ b/gsuite_reports_rules/gsuite_two_step_verification.yml @@ -0,0 +1,46 @@ +AnalysisType: rule +Filename: gsuite_two_step_verification.py +RuleID: GSuite.TwoStepVerification +DisplayName: GSuite User Two Step Verification Change +Enabled: true +LogTypes: + - GSuite.Reports +Tags: + - GSuite +Severity: Low +Description: > + A user disabled two step verification for themselves. +Reference: https://developers.google.com/admin-sdk/reports/v1/appendix/activity/user-accounts +Runbook: > + Depending on company policy, either suggest or require the user re-enable two step verification. +Tests: + - + Name: Two Step Verification Enabled + LogType: GSuites.Reports + ExpectedResult: false + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'user_accounts'}, + 'events': [ + { + 'type': '2sv_change', + 'name': '2sv_enroll' + } + ] + } + - + Name: Two Step Verification Disabled + LogType: GSuites.Reports + ExpectedResult: true + Log: + { + 'actor': {'email': 'bobert@example.com'}, + 'id': {'applicationName': 'user_accounts'}, + 'events': [ + { + 'type': '2sv_change', + 'name': '2sv_disable' + } + ] + }