Detection rules › Panther
GCP User Added to Privileged Group
A user was added to a group with special previleges
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation | T1078.004 Valid Accounts: Cloud Accounts, T1484.001 Domain or Tenant Policy Modification: Group Policy Modification |
Rule body yaml
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"
}
]
}
]
}
}
}
Detection logic
Rule logic imperative Python
from panther_base_helpers import key_value_list_to_dict
PRIVILEGED_GROUPS = {
}
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
params = key_value_list_to_dict(event_.get("parameter", []), "name", "value")
global USER_EMAIL, GROUP_EMAIL
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():
return PRIVILEGED_GROUPS
The parser cannot express this rule's logic as a field filter; the imperative Python above is the detection.
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
email | actor.email |