Detection rules › Panther
Panther rules: zendesk
Enabled Zendesk Support to Assume Users
#User enabled or disabled zendesk support user assumption.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Lateral Movement |
Detection logic
USER_SUSPENSION_ACTIONS = {
"create",
"update",
}
def rule(event):
return (
event.get("source_type") == "account_setting"
and event.get("action", "") in USER_SUSPENSION_ACTIONS
and event.get("source_label", "").lower() in {"account assumption", "assumption duration"}
)
def title(event):
return f"A user [{event.udm('actor_user')}] updated zendesk support user assumption settings"
Rule specification
AnalysisType: rule
Filename: zendesk_user_assumption.py
RuleID: "Zendesk.UserAssumption"
DisplayName: "Enabled Zendesk Support to Assume Users"
Enabled: true
LogTypes:
- Zendesk.Audit
Tags:
- Zendesk
- Lateral Movement:Use Alternate Authentication Material
Reports:
MITRE ATT&CK:
- TA0008:T1550
Severity: Medium
Description: User enabled or disabled zendesk support user assumption.
Runbook: >
Investigate whether allowing zendesk support to assume users is necessary. If not, disable the feature.
Reference: https://support.zendesk.com/hc/en-us/articles/4408894200474-Assuming-end-users#:~:text=In%20Support%2C%20click%20the%20Customers,user%20in%20the%20information%20dialog
SummaryAttributes:
- p_any_ip_addresses
Stages and Predicates
Fires on Zendesk.Audit events when all of the conditions below hold.
Condition
source_typeisaccount_settingactionis one ofcreate,updatesource_labelis one ofaccount assumption,assumption duration
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | in |
| field:"action" kind:in |
source_label | in |
| field:"source_label" kind:in |
source_type | eq |
| field:"source_type" kind:eq value:"account_setting" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
actor_user |
Response runbook
Investigate whether allowing zendesk support to assume users is necessary. If not, disable the feature.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "update",
"action_label": "Updated",
"actor_id": 123,
"actor_name": "John Doe",
"change_description": "Changed",
"created_at": "2021-05-28T18:39:50Z",
"id": 123456789123,
"ip_address": "127.0.0.1",
"p_log_type": "Zendesk.Audit",
"source_id": 123,
"source_label": "Account Assumption",
"source_type": "account_setting",
"url": "https://myzendek.zendesk.com/api/v2/audit_logs/111222333444.json"
}
Zendesk Account Owner Changed
#Only one admin user can be the account owner. Ensure the change in ownership is expected.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Detection logic
import re
from panther_zendesk_helpers import ZENDESK_CHANGE_DESCRIPTION
ZENDESK_OWNER_CHANGED = re.compile(
r"Owner changed from (?P<old_owner>.+) to (?P<new_owner>[^$]+)", re.IGNORECASE
)
def rule(event):
if event.get("action", "") == "update" and event.get("source_type", "") == "account":
return event.get(ZENDESK_CHANGE_DESCRIPTION, "").lower().startswith("owner changed from ")
return False
def title(event):
old_owner = "<UNKNOWN_USER>"
new_owner = "<UNKNOWN_USER>"
matches = ZENDESK_OWNER_CHANGED.match(event.get(ZENDESK_CHANGE_DESCRIPTION, ""))
if matches:
old_owner = matches.group("old_owner")
new_owner = matches.group("new_owner")
return f"zendesk administrative owner changed from {old_owner} to {new_owner}"
Rule specification
AnalysisType: rule
Filename: zendesk_new_owner.py
RuleID: "Zendesk.AccountOwnerChanged"
DedupPeriodMinutes: 60
DisplayName: "Zendesk Account Owner Changed"
Enabled: true
LogTypes:
- Zendesk.Audit
Severity: High
Tags:
- Zendesk
- Privilege Escalation:Valid Accounts
Reports:
MITRE ATT&CK:
- TA0004:T1078
Description: Only one admin user can be the account owner. Ensure the change in ownership is expected.
Reference: https://support.zendesk.com/hc/en-us/articles/4408822084634-Changing-the-account-owner
SummaryAttributes:
- p_any_ip_addresses
Stages and Predicates
Fires on Zendesk.Audit events when all of the conditions below hold.
Condition
actionisupdatesource_typeisaccount
This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"update" |
source_type | eq |
| field:"source_type" kind:eq value:"account" |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "update",
"action_label": "Updated",
"actor_id": 123,
"actor_name": "John Doe",
"change_description": "Owner changed from Bob Cat to Mountain Lion",
"created_at": "2021-05-28T18:39:50Z",
"id": 123456789123,
"ip_address": "127.0.0.1",
"p_log_type": "Zendesk.Audit",
"source_id": 123,
"source_label": "Account: Account",
"source_type": "account",
"url": "https://myzendek.zendesk.com/api/v2/audit_logs/111222333444.json"
}
Zendesk API Token Created
#A user created a new API token to be used with Zendesk.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Detection logic
API_TOKEN_ACTIONS = {
"create",
"destroy",
}
def rule(event):
return event.get("source_type") == "api_token" and event.get("action", "") in API_TOKEN_ACTIONS
def title(event):
action = event.get("action", "<UNKNOWN_ACTION>")
return f"[{event.get('p_log_type')}]: User [{event.udm('actor_user')}] {action} an api token"
def severity(event):
if event.get("action", "") == "destroy":
return "INFO"
return "HIGH"
Rule specification
AnalysisType: rule
Filename: zendesk_new_api_token.py
RuleID: "Zendesk.NewAPIToken"
DedupPeriodMinutes: 60
DisplayName: "Zendesk API Token Created"
Enabled: true
LogTypes:
- Zendesk.Audit
Severity: High
Tags:
- Zendesk
- Credential Access:Steal Application Access Token
Reports:
MITRE ATT&CK:
- TA0006:T1528
Description: A user created a new API token to be used with Zendesk.
Runbook: Validate the api token was created for valid use case, otherwise delete the token immediately.
Reference: https://support.zendesk.com/hc/en-us/articles/4408889192858-Managing-access-to-the-Zendesk-API#topic_bsw_lfg_mmb:~:text=enable%20token%20access.-,Generating%20API%20tokens,-To%20generate%20an
SummaryAttributes:
- p_any_ip_addresses
Stages and Predicates
Fires on Zendesk.Audit events when all of the conditions below hold.
Condition
source_typeisapi_tokenactionis one ofcreate,destroy
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | in |
| field:"action" kind:in |
source_type | eq |
| field:"source_type" kind:eq value:"api_token" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
p_log_type |
actor_user |
action |
Response runbook
Validate the api token was created for valid use case, otherwise delete the token immediately.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "create",
"action_label": "Created",
"actor_id": 123,
"actor_name": "John Doe",
"change_description": "",
"created_at": "2021-05-28T18:39:50Z",
"id": 123456789123,
"ip_address": "127.0.0.1",
"p_log_type": "Zendesk.Audit",
"source_id": 123,
"source_label": "API token",
"source_type": "api_token",
"url": "https://myzendek.zendesk.com/api/v2/audit_logs/111222333444.json"
}
Zendesk Credit Card Redaction Off
#A user updated account setting that disabled credit card redaction.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Collection |
Detection logic
from panther_zendesk_helpers import ZENDESK_CHANGE_DESCRIPTION
REDACTION_ACTIONS = {
"create",
"destroy",
}
def rule(event):
return (
event.get("source_type") == "account_setting"
and event.get("action", "") in REDACTION_ACTIONS
and event.get("source_label", "") == "Credit Card Redaction"
)
def title(event):
action = event.get(ZENDESK_CHANGE_DESCRIPTION, "<UNKNOWN_ACTION>")
return f"User [{event.udm('actor_user')}] {action} credit card redaction"
def severity(event):
if event.get(ZENDESK_CHANGE_DESCRIPTION, "").lower() != "disabled":
return "INFO"
return "HIGH"
Rule specification
AnalysisType: rule
Filename: zendesk_sensitive_data_redaction.py
RuleID: "Zendesk.SensitiveDataRedactionOff"
DedupPeriodMinutes: 60
DisplayName: "Zendesk Credit Card Redaction Off"
Enabled: true
LogTypes:
- Zendesk.Audit
Tags:
- Zendesk
- Collection:Data from Information Repositories
Reports:
MITRE ATT&CK:
- TA0009:T1213
Severity: High
Description: A user updated account setting that disabled credit card redaction.
Runbook: Re-enable credit card redaction.
Reference: https://support.zendesk.com/hc/en-us/articles/4408822124314-Automatically-redacting-credit-card-numbers-from-tickets
SummaryAttributes:
- p_any_ip_addresses
Stages and Predicates
Fires on Zendesk.Audit events when all of the conditions below hold.
Condition
source_typeisaccount_settingactionis one ofcreate,destroysource_labelisCredit Card Redaction
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | in |
| field:"action" kind:in |
source_label | eq |
| field:"source_label" kind:eq value:"Credit Card Redaction" |
source_type | eq |
| field:"source_type" kind:eq value:"account_setting" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
actor_user |
Response runbook
Re-enable credit card redaction.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "create",
"action_label": "Updated",
"actor_id": 123,
"actor_name": "John Doe",
"change_description": "Disabled",
"created_at": "2021-05-28T18:39:50Z",
"id": 123456789123,
"ip_address": "127.0.0.1",
"p_log_type": "Zendesk.Audit",
"source_id": 123,
"source_label": "Credit Card Redaction",
"source_type": "account_setting",
"url": "https://myzendek.zendesk.com/api/v2/audit_logs/111222333444.json"
}
Zendesk Mobile App Access Modified
#A user updated account setting that enabled or disabled mobile app access.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Detection logic
from panther_zendesk_helpers import ZENDESK_CHANGE_DESCRIPTION
MOBILE_APP_ACTIONS = {"create", "update"}
def rule(event):
return (
event.get("source_type") == "account_setting"
and event.get("action", "") in MOBILE_APP_ACTIONS
and event.get("source_label", "") == "Zendesk Support Mobile App Access"
)
def title(event):
action = event.get(ZENDESK_CHANGE_DESCRIPTION, "<UNKNOWN_ACTION>")
return f"User [{event.udm('actor_user')}] {action} mobile app access"
def severity(event):
if event.get(ZENDESK_CHANGE_DESCRIPTION, "").lower() == "disabled":
return "INFO"
return "MEDIUM"
Rule specification
AnalysisType: rule
Filename: zendesk_mobile_app_access.py
RuleID: "Zendesk.MobileAppAccessUpdated"
DedupPeriodMinutes: 60
DisplayName: "Zendesk Mobile App Access Modified"
Enabled: true
LogTypes:
- Zendesk.Audit
Tags:
- Zendesk
- Persistence:Valid Accounts
Reports:
MITRE ATT&CK:
- TA0003:T1078
Severity: Medium
Description: A user updated account setting that enabled or disabled mobile app access.
Reference: https://support.zendesk.com/hc/en-us/articles/4408846407066-About-the-Zendesk-Support-mobile-app#:~:text=More%20settings.-,Configuring%20the%20mobile%20app,-Activate%20the%20new
SummaryAttributes:
- p_any_ip_addresses
Stages and Predicates
Fires on Zendesk.Audit events when all of the conditions below hold.
Condition
source_typeisaccount_settingactionis one ofcreate,updatesource_labelisZendesk Support Mobile App Access
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | in |
| field:"action" kind:in |
source_label | eq |
| field:"source_label" kind:eq value:"Zendesk Support Mobile App Access" |
source_type | eq |
| field:"source_type" kind:eq value:"account_setting" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
actor_user |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "create",
"action_label": "Updated",
"actor_id": 123,
"actor_name": "John Doe",
"change_description": "Disabled",
"created_at": "2021-05-28T18:39:50Z",
"id": 123456789123,
"ip_address": "127.0.0.1",
"p_log_type": "Zendesk.Audit",
"source_id": 123,
"source_label": "Zendesk Support Mobile App Access",
"source_type": "account_setting",
"url": "https://myzendek.zendesk.com/api/v2/audit_logs/111222333444.json"
}
Zendesk User Role Changed
#A user's Zendesk role was changed
Detection logic
import panther_event_type_helpers as event_type
from panther_zendesk_helpers import zendesk_get_roles
def rule(event):
if event.get("source_type") == "user" and event.get("action") == "update":
# admin roles have their own handling
if (
event.udm("event_type") != event_type.ADMIN_ROLE_ASSIGNED
and "role changed" in event.get("change_description", "").lower()
):
_, new_role = zendesk_get_roles(event)
return bool(new_role)
return False
def title(event):
old_role, new_role = zendesk_get_roles(event)
return (
f"Actor user [{event.udm('actor_user')}] changed [{event.udm('user')}] role from "
f"{old_role} to {new_role}"
)
Rule specification
AnalysisType: rule
Filename: zendesk_user_role.py
RuleID: "Zendesk.UserRoleChanged"
DedupPeriodMinutes: 60
DisplayName: "Zendesk User Role Changed"
Enabled: true
LogTypes:
- Zendesk.Audit
Severity: Info
Description: A user's Zendesk role was changed
Reference: https://support.zendesk.com/hc/en-us/articles/4408824375450-Setting-roles-and-access-in-Zendesk-Admin-Center
SummaryAttributes:
- p_any_ip_addresses
Stages and Predicates
Fires on Zendesk.Audit events when all of the conditions below hold.
Condition
source_typeisuseractionisupdateevent_typeis notadmin_role_assignedchange_descriptioncontainsrole changed
This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"update" |
change_description | contains |
| field:"change_description" kind:contains value:"role changed" |
event_type | ne |
| field:"event_type" kind:ne value:"admin_role_assigned" |
source_type | eq |
| field:"source_type" kind:eq value:"user" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
actor_user |
user |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "update",
"action_label": "Updated",
"actor_id": 123,
"actor_name": "John Doe",
"change_description": "Role changed from Administrator to End User",
"created_at": "2021-05-28T18:39:50Z",
"id": 123456789123,
"ip_address": "127.0.0.1",
"p_log_type": "Zendesk.Audit",
"source_id": 123,
"source_label": "Bob Cat",
"source_type": "user",
"url": "https://myzendek.zendesk.com/api/v2/audit_logs/111222333444.json"
}
Zendesk User Suspension Status Changed
#A user's Zendesk suspension status was changed.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Impact |
Detection logic
from panther_zendesk_helpers import ZENDESK_CHANGE_DESCRIPTION
USER_SUSPENSION_ACTIONS = {
"create",
"update",
}
def rule(event):
return (
event.get("source_type") == "user_setting"
and event.get("action", "") in USER_SUSPENSION_ACTIONS
and "suspended" in event.get(ZENDESK_CHANGE_DESCRIPTION, "").lower()
)
def title(event):
suspension_status = event.get(ZENDESK_CHANGE_DESCRIPTION, "").lower()
user = event.get("source_label", "<UNKNOWN_USER>").split(":")
if len(user) > 1:
user = user[1].strip()
return f"Actor user [{event.udm('actor_user')}] {suspension_status} user [{user}]"
def severity(event):
if event.get(ZENDESK_CHANGE_DESCRIPTION, "").lower() == "suspended":
return "INFO"
return "DEFAULT"
Rule specification
AnalysisType: rule
Filename: zendesk_user_suspension.py
RuleID: "Zendesk.UserSuspension"
DedupPeriodMinutes: 60
DisplayName: "Zendesk User Suspension Status Changed"
Enabled: true
LogTypes:
- Zendesk.Audit
Tags:
- Zendesk
- Impact:Account Access Removal
Reports:
MITRE ATT&CK:
- TA0040:T1531
Severity: High
Description: A user's Zendesk suspension status was changed.
Runbook: Ensure the user's suspension status is appropriate.
Reference: https://support.zendesk.com/hc/en-us/articles/4408889293978-Suspending-a-user#:~:text=select%20Unsuspend%20access.-,Identifying%20suspended%20users,name%20on%20the%20Customers%20page
SummaryAttributes:
- p_any_ip_addresses
Stages and Predicates
Fires on Zendesk.Audit events when all of the conditions below hold.
Condition
source_typeisuser_settingactionis one ofcreate,update
This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | in |
| field:"action" kind:in |
source_type | eq |
| field:"source_type" kind:eq value:"user_setting" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
actor_user |
Response runbook
Ensure the user's suspension status is appropriate.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "create",
"action_label": "Updated",
"actor_id": 123,
"actor_name": "John Doe",
"change_description": "Suspended",
"created_at": "2021-05-28T18:39:50Z",
"id": 123456789123,
"ip_address": "127.0.0.1",
"p_log_type": "Zendesk.Audit",
"source_id": 123,
"source_label": "Suspension state: Bob Cat",
"source_type": "user_setting",
"url": "https://myzendek.zendesk.com/api/v2/audit_logs/111222333444.json"
}