Detection rules › Panther
Panther rules: slack
Slack Anomaly Detected
#Passthrough for anomalies detected by Slack
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Command & Control |
Detection logic
from panther_slack_helpers import slack_alert_context
ELEVATED_ANOMALIES = {"excessive_malware_uploads", "session_fingerprint", "unexpected_admin_action"}
def rule(event):
return event.get("action") == "anomaly"
def severity(event):
# Return "MEDIUM" for some more serious anomalies
reasons = event.deep_get("details", "reason", default=[])
if set(reasons) & ELEVATED_ANOMALIES:
return "MEDIUM"
return "DEFAULT"
def title(event):
anomalies = {
"asn": "An ASN was on a list of suspicious ASNs",
"excessive_downloads": "A user downloaded an excessive amount of files",
"excessive_file_shares": "A user shared an excessive amount of files",
"excessive_malware_uploads": "A user uploaded an excessive amount of malware files",
"ip_address": "An anomaly was detected in the IP address used for the user token",
"search_volume": "An unusual volume of search activity was detected",
"session_fingerprint": "The session cookie has an unusual timestamp or client fingerprint",
"spoofed_user_agent": "Characteristics of the client do not match the user agent",
"tor": "A Tor exit node was used",
"unexpected_admin_action": "An unexpected admin action was performed",
"unexpected_api_call_volume": "An unexpected volume of API calls was detected",
"unexpected_client": "An anomalous Slack client was detected",
"unexpected_credential_testing": "Unexpected credential testing activity was detected",
"unexpected_message_deletion": "Unexpected message deletion activity was detected",
"unexpected_scraping": "Unexpected scraping activity was detected",
"unexpected_user_agent": "An unexpected user agent was detected",
"user_agent": "An anomaly was detected in the user agent used for the user token",
}
reasons = event.deep_get("details", "reason", default=[])
reasons_str = reasons[0] if reasons else ""
anomaly_description = anomalies.get(reasons_str)
actor = event.deep_get("actor", "user", "email", default="")
actor_str = f" for {actor}" if actor else ""
if anomaly_description:
return f"Slack Anomaly Detected{actor_str}: {anomaly_description}"
# if the anomaly is not in our list (for future use)
return f"Slack Anomaly Detected{actor_str}"
def alert_context(event):
context = slack_alert_context(event)
context |= {"details": event.get("details", {}), "context": event.get("context", {})}
return context
Rule specification
AnalysisType: rule
Filename: slack_passthrough_anomaly.py
RuleID: "Slack.AuditLogs.PassthroughAnomaly"
DisplayName: "Slack Anomaly Detected"
Enabled: true
LogTypes:
- Slack.AuditLogs
Severity: Low
Reports:
MITRE ATT&CK:
- TA0011:T1071
Description: Passthrough for anomalies detected by Slack
DedupPeriodMinutes: 60
Threshold: 1
Reference:
https://api.slack.com/admins/audit-logs-anomaly
SummaryAttributes:
- p_any_ip_addresses
- p_any_emails
Tags:
- Slack
- Command and Control
- Application Layer Protocol
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionisanomaly
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"anomaly" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "anomaly",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "W012J3FEWAU",
"name": "primary-owner",
"team": "T01234N56GB"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-workspace-1",
"id": "T01234N56GB",
"name": "test-workspace-1",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
}
Slack App Access Expanded
#Detects when a Slack App has had its permission scopes expanded
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Detection logic
from panther_slack_helpers import slack_alert_context
ACCESS_EXPANDED_ACTIONS = [
"app_scopes_expanded",
"app_resources_added",
"app_resources_granted",
"bot_token_upgraded",
]
def rule(event):
if event.get("action") not in ACCESS_EXPANDED_ACTIONS:
return False
# Check to confirm that app scopes actually expanded or not
if event.get("action") == "app_scopes_expanded":
changes = get_scope_changes(event)
if not changes["added"]:
return False
return True
def title(event):
return (
f"Slack App [{event.deep_get('entity', 'app', 'name')}] "
f"Access Expanded by [{event.deep_get('actor', 'user', 'name')}]"
)
def alert_context(event):
context = slack_alert_context(event)
changes = get_scope_changes(event)
context["scopes_added"] = changes["added"]
context["scopes_removed"] = changes["removed"]
return context
def get_scope_changes(event) -> dict[str, list[str]]:
changes = {}
new_scopes = event.deep_get("details", "new_scopes", default=[])
prv_scopes = event.deep_get("details", "previous_scopes", default=[])
changes["added"] = [x for x in new_scopes if x not in prv_scopes]
changes["removed"] = [x for x in prv_scopes if x not in new_scopes]
return changes
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 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 event.deep_get("details", "new_scopes", default=[]):
return "High"
if "admin" in event.deep_get("details", "bot_scopes", default=[]):
return "High"
return "Medium"
Rule specification
AnalysisType: rule
Filename: slack_app_access_expanded.py
RuleID: "Slack.AuditLogs.AppAccessExpanded"
DisplayName: "Slack App Access Expanded"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Privilege Escalation
- Account Manipulation
Reports:
MITRE ATT&CK:
- TA0004:T1098
Severity: Medium
Description: Detects when a Slack App has had its permission scopes expanded
Reference: https://slack.com/intl/en-gb/help/articles/1500009181142-Manage-app-settings-and-permissions
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- action
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionis one ofapp_scopes_expanded,app_resources_added,app_resources_granted,bot_token_upgraded
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 |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
name | entity.app.name |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "app_scopes_expanded",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "A012B3CDEFG",
"name": "username",
"team": "T01234N56GB"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-workspace",
"id": "T01234N56GB",
"name": "test-workspace",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
},
"date_create": "2022-07-28 16:48:14",
"details": {
"granular_bot_token": true,
"is_internal_integration": false,
"is_token_rotation_enabled_app": false,
"new_scopes": [
"app_mentions:read",
"channels:join",
"channels:read",
"chat:write",
"chat:write.public",
"team:read",
"users:read",
"im:history",
"groups:read",
"reactions:write",
"groups:history",
"channels:history"
],
"previous_scopes": [
"app_mentions:read",
"commands",
"channels:join",
"channels:read",
"chat:write",
"chat:write.public",
"users:read",
"groups:read",
"reactions:write",
"groups:history",
"channels:history"
]
},
"entity": {
"type": "workspace",
"workspace": {
"domain": "test-workspace-1",
"id": "T01234N56GB",
"name": "test-workspace-1"
}
},
"id": "9d9b76ce-47bb-4838-a96a-1b5fd4d1b564"
}
Slack App Added
#Detects when a Slack App has been added to a workspace
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Detection logic
from panther_slack_helpers import slack_alert_context
APP_ADDED_ACTIONS = [
"app_approved",
"app_installed",
"org_app_workspace_added",
]
def rule(event):
return event.get("action") in APP_ADDED_ACTIONS
def title(event):
return (
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"] = event.deep_get("entity", "app", "scopes")
return context
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 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 event.deep_get("details", "new_scopes", default=[]):
return "High"
if "admin" in event.deep_get("details", "bot_scopes", default=[]):
return "High"
return "Medium"
Rule specification
AnalysisType: rule
Filename: slack_app_added.py
RuleID: "Slack.AuditLogs.AppAdded"
DisplayName: "Slack App Added"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Persistence
- Server Software Component
Reports:
MITRE ATT&CK:
- TA0003:T1505
Severity: Medium
Description: Detects when a Slack App has been added to a workspace
Reference: https://slack.com/intl/en-gb/help/articles/202035138-Add-apps-to-your-Slack-workspace
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionis one ofapp_approved,app_installed,org_app_workspace_added
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | in |
| field:"action" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
name | entity.app.name |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "app_installed",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "W012J3FEWAU",
"name": "primary-owner",
"team": "E012MH3HS94"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-panther-1",
"id": "T01770N79GB",
"name": "test-workspace-1",
"type": "workspace"
},
"ua": "Go-http-client/2.0"
},
"date_create": "2021-06-08 22:16:15",
"details": {
"is_internal_integration": false,
"is_token_rotation_enabled_app": false
},
"entity": {
"app": {
"id": "A049JV0H0KC",
"is_directory_approved": true,
"is_distributed": true,
"name": "Notion",
"scopes": [
"channels:history",
"channels:read",
"chat:write",
"groups:read",
"groups:write",
"im:read",
"mpim:read",
"groups:history",
"im:history",
"mpim:history"
]
},
"type": "app"
}
}
Slack App Removed
#Detects when a Slack App has been removed
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Stealth | |
| Impact |
Detection logic
from panther_slack_helpers import slack_alert_context
APP_REMOVED_ACTIONS = [
"app_restricted",
"app_uninstalled",
"org_app_workspace_removed",
]
def rule(event):
return event.get("action") in APP_REMOVED_ACTIONS
def title(event):
return (
f"Slack App [{event.deep_get('entity', 'app', 'name')}] "
f"Removed by [{event.deep_get('actor', 'user', 'name')}]"
)
def alert_context(event):
return slack_alert_context(event)
Rule specification
AnalysisType: rule
Filename: slack_app_removed.py
RuleID: "Slack.AuditLogs.AppRemoved"
DisplayName: "Slack App Removed"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Impact
- Service Stop
- Defense Evasion
- Indicator Removal
- Clear Persistence
Reports:
MITRE ATT&CK:
- TA0040:T1489
- TA0005:T1070.009
Severity: Medium
Description: Detects when a Slack App has been removed
Reference: https://slack.com/intl/en-gb/help/articles/360003125231-Remove-apps-and-customised-integrations-from-your-workspace
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionis one ofapp_restricted,app_uninstalled,org_app_workspace_removed
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | in |
| field:"action" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
name | entity.app.name |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "app_restricted",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "W012J3FEWAU",
"name": "primary-owner",
"team": "E012MH3HS94"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-panther-1",
"id": "T01770N79GB",
"name": "test-workspace-1",
"type": "workspace"
},
"ua": "Go-http-client/2.0"
},
"date_create": "2021-06-08 22:16:15",
"details": {
"app_owner_id": "W012J3AEWAU",
"is_internal_integration": true
},
"entity": {
"app": {
"id": "A012F34BFEF",
"is_directory_approved": false,
"is_distributed": false,
"name": "app-name",
"scopes": [
"admin"
]
},
"type": "app"
}
}
Slack Denial of Service via Session Invalidation
#Detects potential DoS attacks via excessive session invalidation when administrators reset user sessions 60+ times within 24 hours. Repeated session termination prevents users from maintaining Slack access, disrupting communication and productivity. Legitimate session resets for incident response or troubleshooting typically occur 1-3 times, so reaching the 60-event threshold indicates malicious intent.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Impact |
Detection logic
from panther_slack_helpers import slack_alert_context
DENIAL_OF_SERVICE_ACTIONS = [
"bulk_session_reset_by_admin",
"user_session_invalidated",
"user_session_reset_by_admin",
]
def rule(event):
# Only evaluate actions that could be used for a DoS
if event.get("action") not in DENIAL_OF_SERVICE_ACTIONS:
return False
return True
def title(event):
admin = event.deep_get("actor", "user", "email", default="<UNKNOWN_ADMIN>")
target = event.deep_get("entity", "user", "name", default="<UNKNOWN_USER>")
action = event.get("action", "<UNKNOWN_ACTION>")
return f"Slack: Potential DoS - Admin [{admin}] performed [{action}] on user [{target}]"
def dedup(event):
return f"Slack.AuditLogs.ApplicationDoS{event.deep_get('entity', 'user', 'name')}"
def alert_context(event):
return slack_alert_context(event)
Rule specification
AnalysisType: rule
Filename: slack_application_dos.py
RuleID: "Slack.AuditLogs.ApplicationDoS"
DisplayName: "Slack Denial of Service via Session Invalidation"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Impact
- Endpoint Denial of Service
- Application Exhaustion Flood
Reports:
MITRE ATT&CK:
- TA0040:T1499.003
Severity: Critical
Description: >
Detects potential DoS attacks via excessive session invalidation when administrators reset user sessions 60+ times within 24 hours. Repeated session termination prevents users from maintaining Slack access, disrupting communication and productivity. Legitimate session resets for incident response or troubleshooting typically occur 1-3 times, so reaching the 60-event threshold indicates malicious intent.
Reference: https://slack.com/intl/en-gb/help/articles/115005223763-Manage-session-duration-#pro-and-business+-subscriptions-2
Runbook: |
1. Query Slack audit logs for all actions by actor.user.email in the 7 days around this event to identify other malicious activities such as unauthorized user removals, workspace settings changes, data exports, or app installations indicating compromised admin account
2. Review the total number of session reset events targeting entity.user.name and the time span to calculate the frequency and determine if this represents a sustained denial of service attack
3. Search Slack audit logs for session reset patterns targeting other users to determine if this is an isolated incident or part of a broader campaign affecting multiple users
DedupPeriodMinutes: 1440
Threshold: 60
SummaryAttributes:
- action
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionis one ofbulk_session_reset_by_admin,user_session_invalidated,user_session_reset_by_admin
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | in |
| field:"action" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
action | |
name | entity.user.name |
Response runbook
1. Query Slack audit logs for all actions by actor.user.email in the 7 days around this event to identify other malicious activities such as unauthorized user removals, workspace settings changes, data exports, or app installations indicating compromised admin account
2. Review the total number of session reset events targeting entity.user.name and the time span to calculate the frequency and determine if this represents a sustained denial of service attack
3. Search Slack audit logs for session reset patterns targeting other users to determine if this is an isolated incident or part of a broader campaign affecting multiple users
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "user_session_reset_by_admin",
"actor": {
"type": "user",
"user": {
"email": "admin@example.com",
"id": "W012J3FEWAU",
"name": "primary-owner",
"team": "T01234N56GB"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-workspace-1",
"id": "T01234N56GB",
"name": "test-workspace-1",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
},
"entity": {
"type": "user",
"user": {
"email": "target@example.com",
"id": "U987654321",
"name": "target-user",
"team": "T01234N56GB"
}
}
}
Slack DLP Modified
#Detects when a Data Loss Prevention (DLP) rule has been deactivated or a violation has been deleted
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Stealth | |
| Defense Impairment |
Detection logic
from panther_slack_helpers import slack_alert_context
DLP_ACTIONS = [
"native_dlp_rule_deactivated",
"native_dlp_violation_deleted",
]
def rule(event):
return event.get("action") in DLP_ACTIONS
def title(event):
if event.get("action") == "native_dlp_rule_deactivated":
return "Slack DLP Rule Deactivated"
return "Slack DLP Violation Deleted"
# DLP violations can be removed by security engineers in the case of FPs
# We still want to alert on these, however those should not constitute a High severity
def severity(event):
if event.get("action") == "native_dlp_violation_deleted":
return "Medium"
return "High"
def alert_context(event):
return slack_alert_context(event)
Rule specification
AnalysisType: rule
Filename: slack_dlp_modified.py
RuleID: "Slack.AuditLogs.DLPModified"
DisplayName: "Slack DLP Modified"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Defense Evasion
- Impair Defenses
- Disable or Modify Tools
- Indicator Removal
Reports:
MITRE ATT&CK:
- TA0005:T1562.001
- TA0005:T1070
Severity: High
Description: >
Detects when a Data Loss Prevention (DLP) rule has been deactivated or a violation has been deleted
Reference: https://slack.com/intl/en-gb/help/articles/12914005852819-Slack-Connect--Data-loss-prevention
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- action
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionis one ofnative_dlp_rule_deactivated,native_dlp_violation_deleted
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | in |
| field:"action" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "native_dlp_rule_deactivated",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "A012B3CDEFG",
"name": "username",
"team": "T01234N56GB"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-workspace",
"id": "T01234N56GB",
"name": "test-workspace",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
}
Slack EKM Config Changed
#Detects when the logging settings for a workspace's EKM configuration has changed
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Detection logic
from panther_slack_helpers import slack_alert_context
def rule(event):
# Only alert on the `ekm_logging_config_set` action
return event.get("action") == "ekm_logging_config_set"
def alert_context(event):
# TODO: Add details to the context
return slack_alert_context(event)
Rule specification
AnalysisType: rule
Filename: slack_ekm_config_changed.py
RuleID: "Slack.AuditLogs.EKMConfigChanged"
DisplayName: "Slack EKM Config Changed"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Defense Evasion
- Impair Defenses
- Disable or Modify Cloud Logs
Reports:
MITRE ATT&CK:
- TA0005:T1562.008
Severity: High
Description: Detects when the logging settings for a workspace's EKM configuration has changed
Reference: https://slack.com/intl/en-gb/help/articles/360019110974-Slack-Enterprise-Key-Management
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionisekm_logging_config_set
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"ekm_logging_config_set" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "ekm_logging_config_set",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "A012B3CDEFG",
"name": "username",
"team": "T01234N56GB"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-workspace",
"id": "T01234N56GB",
"name": "test-workspace",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
}
Slack EKM Slackbot Unenrolled
#Detects when a workspace is longer enrolled in EKM
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Impact |
Detection logic
from panther_slack_helpers import slack_alert_context
def rule(event):
# Only alert on the `ekm_slackbot_unenroll_notification_sent` action
return event.get("action") == "ekm_slackbot_unenroll_notification_sent"
def alert_context(event):
return slack_alert_context(event)
Rule specification
AnalysisType: rule
Filename: slack_ekm_slackbot_unenrolled.py
RuleID: "Slack.AuditLogs.EKMSlackbotUnenrolled"
DisplayName: "Slack EKM Slackbot Unenrolled"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Impact
- Service Stop
Reports:
MITRE ATT&CK:
- TA0040:T1489
Severity: High
Description: Detects when a workspace is longer enrolled in EKM
Reference: https://slack.com/intl/en-gb/help/articles/360019110974-Slack-Enterprise-Key-Management
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionisekm_slackbot_unenroll_notification_sent
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"ekm_slackbot_unenroll_notification_sent" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "ekm_slackbot_unenroll_notification_sent",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "A012B3CDEFG",
"name": "username",
"team": "T01234N56GB"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-workspace",
"id": "T01234N56GB",
"name": "test-workspace",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
}
Slack Enterprise Key Management Unenrolled
#Detects when Slack Enterprise Key Management (EKM) is unenrolled, removing customer-controlled encryption and reverting to Slack-managed keys. EKM allows organizations to store encryption keys externally (e.g., AWS KMS), ensuring data remains protected even from Slack infrastructure compromise. Unenrollment exposes all workspace data to decryption by Slack systems and violates compliance requirements for regulated industries.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment | |
| Collection | |
| Exfiltration |
Detection logic
from panther_slack_helpers import slack_alert_context
def rule(event):
# Only alert on the `ekm_unenrolled` action
return event.get("action") == "ekm_unenrolled"
def title(event):
actor = event.deep_get("actor", "user", "email", default="<UNKNOWN_ACTOR>")
workspace = event.deep_get("context", "location", "domain", default="<UNKNOWN_WORKSPACE>")
return (
f"Slack: Workspace [{workspace}] unenrolled from Enterprise Key Management "
f"by [{actor}] - Customer-controlled encryption disabled"
)
def alert_context(event):
return slack_alert_context(event)
Rule specification
AnalysisType: rule
Filename: slack_ekm_unenrolled.py
RuleID: "Slack.AuditLogs.EKMUnenrolled"
DisplayName: "Slack Enterprise Key Management Unenrolled"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Defense Evasion
- Weaken Encryption
- Compliance
- Data Protection
Reports:
MITRE ATT&CK:
- TA0005:T1600
- TA0009:T1530
- TA0010:T1567
Severity: Critical
Description: >
Detects when Slack Enterprise Key Management (EKM) is unenrolled, removing customer-controlled encryption and reverting to Slack-managed keys. EKM allows organizations to store encryption keys externally (e.g., AWS KMS), ensuring data remains protected even from Slack infrastructure compromise. Unenrollment exposes all workspace data to decryption by Slack systems and violates compliance requirements for regulated industries.
Reference: https://slack.com/intl/en-gb/help/articles/360019110974-Slack-Enterprise-Key-Management
Runbook: |
1. Query Slack audit logs for all actions by actor.user.email in the 30 days around the EKM unenrollment to identify other suspicious administrative actions such as data exports, workspace settings changes, user privilege escalations, or API token creations
2. Check if the unenrollment occurred outside normal business hours or from an unusual context.ip_address or geographic location that doesn't match the actor's typical access patterns
3. Search Slack audit logs for data export events, file downloads, or external sharing modifications during the period when EKM was unenrolled to assess potential data exposure while customer-controlled encryption was disabled
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionisekm_unenrolled
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"ekm_unenrolled" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
domain | context.location.domain |
Response runbook
1. Query Slack audit logs for all actions by actor.user.email in the 30 days around the EKM unenrollment to identify other suspicious administrative actions such as data exports, workspace settings changes, user privilege escalations, or API token creations
2. Check if the unenrollment occurred outside normal business hours or from an unusual context.ip_address or geographic location that doesn't match the actor's typical access patterns
3. Search Slack audit logs for data export events, file downloads, or external sharing modifications during the period when EKM was unenrolled to assess potential data exposure while customer-controlled encryption was disabled
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "ekm_unenrolled",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "A012B3CDEFG",
"name": "username",
"team": "T01234N56GB"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-workspace",
"id": "T01234N56GB",
"name": "test-workspace",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
}
Slack IDP Configuration Changed
#Detects changes to the identity provider (IdP) configuration for Slack organizations.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Credential Access |
Detection logic
from panther_slack_helpers import slack_alert_context
IDP_CHANGE_ACTIONS = {
"idp_configuration_added": "Slack IDP Configuration Added",
"idp_configuration_deleted": "Slack IDP Configuration Deleted",
"idp_prod_configuration_updated": "Slack IDP Configuration Updated",
}
def rule(event):
return event.get("action") in IDP_CHANGE_ACTIONS
def title(event):
if event.get("action") in IDP_CHANGE_ACTIONS:
return IDP_CHANGE_ACTIONS.get(event.get("action"))
return "Slack IDP Configuration Changed"
def alert_context(event):
return slack_alert_context(event)
Rule specification
AnalysisType: rule
Filename: slack_idp_configuration_change.py
RuleID: "Slack.AuditLogs.IDPConfigurationChanged"
DisplayName: "Slack IDP Configuration Changed"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Persistence
- Credential Access
- Modify Authentication Process
Reports:
MITRE ATT&CK:
- TA0003:T1556
- TA0006:T1556
Severity: High
Description: Detects changes to the identity provider (IdP) configuration for Slack organizations.
Reference: https://slack.com/intl/en-gb/help/articles/115001435788-Connect-identity-provider-groups-to-your-Enterprise-Grid-org
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- action
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionis one ofidp_configuration_added,idp_configuration_deleted,idp_prod_configuration_updated
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | in |
| field:"action" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
action |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "idp_configuration_added",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "A012B3CDEFG",
"name": "username",
"team": "T01234N56GB"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-workspace",
"id": "T01234N56GB",
"name": "test-workspace",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
},
"date_create": "2022-07-28 16:48:14"
}
Slack Information Barrier Modified
#Detects when a Slack information barrier is deleted/updated
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Detection logic
from panther_slack_helpers import slack_alert_context
INFORMATION_BARRIER_ACTIONS = {
"barrier_deleted": "Slack Information Barrier Deleted",
"barrier_updated": "Slack Information Barrier Updated",
}
def rule(event):
return event.get("action") in INFORMATION_BARRIER_ACTIONS
def title(event):
if event.get("action") in INFORMATION_BARRIER_ACTIONS:
return INFORMATION_BARRIER_ACTIONS.get(event.get("action"))
return "Slack Information Barrier Modified"
def alert_context(event):
return slack_alert_context(event)
Rule specification
AnalysisType: rule
Filename: slack_information_barrier_modified.py
RuleID: "Slack.AuditLogs.InformationBarrierModified"
DisplayName: "Slack Information Barrier Modified"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Defense Evasion
- Impair Defenses
- Disable or Modify Tools
Reports:
MITRE ATT&CK:
- TA0005:T1562.001
Severity: Medium
Description: Detects when a Slack information barrier is deleted/updated
Reference: https://slack.com/intl/en-gb/help/articles/360056171734-Create-information-barriers-in-Slack
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- action
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionis one ofbarrier_deleted,barrier_updated
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | in |
| field:"action" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
action |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "barrier_deleted",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "A012B3CDEFG",
"name": "username",
"team": "T01234N56GB"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-workspace",
"id": "T01234N56GB",
"name": "test-workspace",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
}
Slack Legal Hold Policy Modified
#Detects changes to configured legal hold policies
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment |
Detection logic
from panther_slack_helpers import slack_alert_context
LEGAL_HOLD_POLICY_ACTIONS = {
"legal_hold_policy_entities_deleted": "Slack Legal Hold Policy Entities Deleted",
"legal_hold_policy_exclusion_added": "Slack Exclusions Added to Legal Hold Policy",
"legal_hold_policy_released": "Slack Legal Hold Released",
"legal_hold_policy_updated": "Slack Legal Hold Updated",
}
def rule(event):
return event.get("action") in LEGAL_HOLD_POLICY_ACTIONS
def title(event):
# Only the `legal_hold_policy_updated` event includes relevant data to deduplicate
if event.get("action") == "legal_hold_policy_updated":
return (
f"Slack Legal Hold Updated "
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"))
return "Slack Legal Hold Policy Modified"
def alert_context(event):
return slack_alert_context(event)
Rule specification
AnalysisType: rule
Filename: slack_legal_hold_policy_modified.py
RuleID: "Slack.AuditLogs.LegalHoldPolicyModified"
DisplayName: "Slack Legal Hold Policy Modified"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Defense Evasion
- Impair Defenses
- Disable or Modify Tools
Reports:
MITRE ATT&CK:
- TA0005:T1562.001
Severity: High
Description: Detects changes to configured legal hold policies
Reference: https://slack.com/intl/en-gb/help/articles/4401830811795-Create-and-manage-legal-holds
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionis one oflegal_hold_policy_entities_deleted,legal_hold_policy_exclusion_added,legal_hold_policy_released,legal_hold_policy_updated
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | in |
| field:"action" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
name | details.old_legal_hold_policy.name |
action |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "legal_hold_policy_entities_deleted",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "A012B3CDEFG",
"name": "username",
"team": "T01234N56GB"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-workspace",
"id": "T01234N56GB",
"name": "test-workspace",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
}
Slack MFA Settings Changed
#Detects changes to Multi-Factor Authentication requirements
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Defense Impairment | |
| Credential Access |
Detection logic
from panther_slack_helpers import slack_alert_context
def rule(event):
return event.get("action") == "pref.two_factor_auth_changed"
def alert_context(event):
# TODO: Add details to context
return slack_alert_context(event)
Rule specification
AnalysisType: rule
Filename: slack_mfa_settings_changed.py
RuleID: "Slack.AuditLogs.MFASettingsChanged"
DisplayName: "Slack MFA Settings Changed"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Defense Evasion
- Modify Authentication Process
- Multi-Factor Authentication
Reports:
MITRE ATT&CK:
- TA0005:T1556.006
Severity: High
Description: Detects changes to Multi-Factor Authentication requirements
Reference: https://slack.com/intl/en-gb/help/articles/204509068-Set-up-two-factor-authentication
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionispref.two_factor_auth_changed
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"pref.two_factor_auth_changed" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "pref.two_factor_auth_changed",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "A012B3CDEFG",
"name": "username",
"team": "T01234N56GB"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-workspace",
"id": "T01234N56GB",
"name": "test-workspace",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
}
Slack Microsoft Intune Mobile Device Management Disabled
#Detects when Microsoft Intune MDM integration is disabled for Slack, removing mobile security controls and enabling data exfiltration via unmanaged devices. Intune enforces policies preventing copy/paste to unmanaged apps, requires device encryption, blocks jailbroken devices, and enables remote wipe. Disabling these controls allows unrestricted Slack access from personal or compromised devices without security restrictions.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Defense Impairment | |
| Exfiltration |
Detection logic
from panther_slack_helpers import slack_alert_context
def rule(event):
return event.get("action") == "intune_disabled"
def title(event):
actor = event.deep_get("actor", "user", "email", default="<UNKNOWN_ACTOR>")
workspace = event.deep_get("context", "location", "domain", default="<UNKNOWN_WORKSPACE>")
return (
f"Microsoft Intune: MDM disabled for Slack workspace [{workspace}] "
f"by [{actor}] - Mobile security controls removed"
)
def alert_context(event):
return slack_alert_context(event)
Rule specification
AnalysisType: rule
Filename: slack_intune_mdm_disabled.py
RuleID: "Slack.AuditLogs.IntuneMDMDisabled"
DisplayName: "Slack Microsoft Intune Mobile Device Management Disabled"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Defense Evasion
- Impair Defenses
- Disable or Modify Tools
- Mobile Security
- Data Loss Prevention
Reports:
MITRE ATT&CK:
- TA0005:T1562.001
- TA0010:T1567
Severity: Critical
Description: >
Detects when Microsoft Intune MDM integration is disabled for Slack, removing mobile security controls and enabling data exfiltration via unmanaged devices. Intune enforces policies preventing copy/paste to unmanaged apps, requires device encryption, blocks jailbroken devices, and enables remote wipe. Disabling these controls allows unrestricted Slack access from personal or compromised devices without security restrictions.
Reference: https://slack.com/intl/en-gb/help/articles/6495319642387-Set-up-Slack-for-Intune-mobile-apps
Runbook: |
1. Query Slack audit logs for all security control modifications by actor.user.email in the 30 days around this event including EKM changes, data retention policy modifications, export permission changes, and session management settings to identify a pattern of defense evasion
2. Search Slack audit logs for mobile app login events, file downloads, and data exports during the period when Intune MDM was disabled to detect potential data exfiltration via unmanaged mobile devices
3. Review Microsoft Intune admin logs and Azure AD audit logs for correlated suspicious activity by the same actor such as removing devices from management or disabling other mobile security policies
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionisintune_disabled
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"intune_disabled" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
domain | context.location.domain |
Response runbook
1. Query Slack audit logs for all security control modifications by actor.user.email in the 30 days around this event including EKM changes, data retention policy modifications, export permission changes, and session management settings to identify a pattern of defense evasion
2. Search Slack audit logs for mobile app login events, file downloads, and data exports during the period when Intune MDM was disabled to detect potential data exfiltration via unmanaged mobile devices
3. Review Microsoft Intune admin logs and Azure AD audit logs for correlated suspicious activity by the same actor such as removing devices from management or disabling other mobile security policies
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "intune_disabled",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "A012B3CDEFG",
"name": "username",
"team": "T01234N56GB"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-workspace",
"id": "T01234N56GB",
"name": "test-workspace",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
}
Slack Organization Created
#Detects when a Slack organization is created
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence |
Detection logic
from panther_slack_helpers import slack_alert_context
def rule(event):
return event.get("action") == "organization_created"
def alert_context(event):
return slack_alert_context(event)
Rule specification
AnalysisType: rule
Filename: slack_org_created.py
RuleID: "Slack.AuditLogs.OrgCreated"
DisplayName: "Slack Organization Created"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Persistence
- Create Account
Reports:
MITRE ATT&CK:
- TA0003:T1136
Severity: Low
Description: Detects when a Slack organization is created
Reference: https://slack.com/intl/en-gb/help/articles/206845317-Create-a-Slack-workspace
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionisorganization_created
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"organization_created" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "organization_created",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "A012B3CDEFG",
"name": "username",
"team": "T01234N56GB"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-workspace",
"id": "T01234N56GB",
"name": "test-workspace",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
}
Slack Organization Deleted
#Detects when a Slack organization is deleted
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Impact |
Detection logic
from panther_slack_helpers import slack_alert_context
def rule(event):
return event.get("action") == "organization_deleted"
def alert_context(event):
return slack_alert_context(event)
Rule specification
AnalysisType: rule
Filename: slack_org_deleted.py
RuleID: "Slack.AuditLogs.OrgDeleted"
DisplayName: "Slack Organization Deleted"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Impact
- Account Access Removal
Reports:
MITRE ATT&CK:
- TA0040:T1531
Severity: Medium
Description: Detects when a Slack organization is deleted
Reference: https://slack.com/intl/en-gb/help/articles/204067366-Delete-a-workspace
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionisorganization_deleted
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"organization_deleted" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "organization_deleted",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "A012B3CDEFG",
"name": "username",
"team": "T01234N56GB"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-workspace",
"id": "T01234N56GB",
"name": "test-workspace",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
}
Slack Primary Owner Transferred
#Detects Slack Primary Owner transfers, representing the highest administrative privilege change with absolute control over workspace settings, security, billing, and data access. Primary Owners can add/remove all admins, delete entire workspaces, and transfer ownership. Unauthorized transfers indicate account compromise, insider threats, or hostile takeovers that could lead to permanent data loss or complete security control loss.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Privilege Escalation | |
| Stealth | |
| Impact |
Detection logic
from panther_slack_helpers import slack_alert_context
def rule(event):
return event.get("action") == "service_owner_transferred"
def title(event):
previous_owner = event.deep_get("actor", "user", "email", default="<UNKNOWN_PREVIOUS_OWNER>")
new_owner = event.deep_get("entity", "user", "email", default="<UNKNOWN_NEW_OWNER>")
workspace = event.deep_get("context", "location", "domain", default="<UNKNOWN_WORKSPACE>")
return (
f"Slack: Primary Owner transferred for workspace [{workspace}] "
f"from [{previous_owner}] to [{new_owner}] - Highest privilege transfer"
)
def alert_context(event):
return slack_alert_context(event)
Rule specification
AnalysisType: rule
Filename: slack_service_owner_transferred.py
RuleID: "Slack.AuditLogs.ServiceOwnerTransferred"
DisplayName: "Slack Primary Owner Transferred"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Defense Evasion
- Persistence
- Account Manipulation
- Impact
- Account Access Removal
- Privilege Escalation
Reports:
MITRE ATT&CK:
- TA0004:T1078.004
- TA0003:T1098
- TA0040:T1531
Severity: Critical
Description: >
Detects Slack Primary Owner transfers, representing the highest administrative privilege change with absolute control over workspace settings, security, billing, and data access. Primary Owners can add/remove all admins, delete entire workspaces, and transfer ownership. Unauthorized transfers indicate account compromise, insider threats, or hostile takeovers that could lead to permanent data loss or complete security control loss.
Reference: https://slack.com/intl/en-gb/help/articles/204401633-Transfer-ownership-of-a-workspace-or-org
Runbook: |
1. Query Slack audit logs for both actor.user.email and entity.user.email in the 30 days before the ownership transfer to identify suspicious authentication patterns, logins from unusual locations, or unexpected administrative actions indicating account compromise
2. Check if entity.user.email has made any concerning administrative changes since receiving ownership such as removing administrators, disabling security controls, adding unknown users, or creating API tokens
3. Review the context.ip_address and user agent from where the transfer was initiated to determine if it matches the previous Primary Owner's normal access patterns and geographic location
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionisservice_owner_transferred
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"service_owner_transferred" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
domain | context.location.domain |
email | entity.user.email |
Response runbook
1. Query Slack audit logs for both actor.user.email and entity.user.email in the 30 days before the ownership transfer to identify suspicious authentication patterns, logins from unusual locations, or unexpected administrative actions indicating account compromise
2. Check if entity.user.email has made any concerning administrative changes since receiving ownership such as removing administrators, disabling security controls, adding unknown users, or creating API tokens
3. Review the context.ip_address and user agent from where the transfer was initiated to determine if it matches the previous Primary Owner's normal access patterns and geographic location
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "service_owner_transferred",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "A012B3CDEFG",
"name": "username",
"team": "T01234N56GB"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-workspace",
"id": "T01234N56GB",
"name": "test-workspace",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
}
Slack Private Channel Made Public
#Detects when a channel that was previously private is made public
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Defense Impairment | |
| Exfiltration |
Detection logic
from panther_slack_helpers import slack_alert_context
def rule(event):
return event.get("action") == "private_channel_converted_to_public"
def alert_context(event):
return slack_alert_context(event)
def title(event):
channel_name = event.deep_get("entity", "channel", "name", default="<unknown_channel>")
name = event.deep_get("actor", "user", "name", default="<unknown_user>")
email = event.deep_get("actor", "user", "email", default="<unknown_email>")
return f"Slack private channel {channel_name} made public by {name} <{email}>"
Rule specification
AnalysisType: rule
Filename: slack_private_channel_made_public.py
RuleID: "Slack.AuditLogs.PrivateChannelMadePublic"
DisplayName: "Slack Private Channel Made Public"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Defense Evasion
- File and Directory Permissions Modification
- Persistence
- Account Manipulation
- Exfiltration
- Exfiltration Over Web Service
Reports:
MITRE ATT&CK:
- TA0005:T1222
- TA0003:T1098
- TA0010:T1567
Severity: High
Description: Detects when a channel that was previously private is made public
Reference: https://slack.com/intl/en-gb/help/articles/213185467-Convert-a-channel-to-private-or-public
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionisprivate_channel_converted_to_public
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"private_channel_converted_to_public" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
name | entity.channel.name |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "private_channel_converted_to_public",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "A012B3CDEFG",
"name": "username",
"team": "T01234N56GB"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-workspace",
"id": "T01234N56GB",
"name": "test-workspace",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
}
Slack SSO Settings Changed
#Detects changes to Single Sign On (SSO) restrictions
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Credential Access |
Detection logic
from panther_slack_helpers import slack_alert_context
def rule(event):
return event.get("action") == "pref.sso_setting_changed"
def alert_context(event):
# TODO: Add details to context
return slack_alert_context(event)
Rule specification
AnalysisType: rule
Filename: slack_sso_settings_changed.py
RuleID: "Slack.AuditLogs.SSOSettingsChanged"
DisplayName: "Slack SSO Settings Changed"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Credential Access
- Persistence
- Modify Authentication Process
Reports:
MITRE ATT&CK:
- TA0003:T1556
- TA0006:T1556
Severity: High
Description: Detects changes to Single Sign On (SSO) restrictions
Reference: https://slack.com/intl/en-gb/help/articles/220403548-Manage-single-sign-on-settings
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionispref.sso_setting_changed
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"pref.sso_setting_changed" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "pref.sso_setting_changed",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "A012B3CDEFG",
"name": "username",
"team": "T01234N56GB"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-workspace",
"id": "T01234N56GB",
"name": "test-workspace",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
}
}
Slack User Privilege Escalation
#Detects when a Slack user gains escalated privileges
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Detection logic
from panther_slack_helpers import slack_alert_context
USER_PRIV_ESC_ACTIONS = {
"owner_transferred": "Slack Owner Transferred",
"permissions_assigned": "Slack User Assigned Permissions",
"role_change_to_admin": "Slack User Made Admin",
"role_change_to_owner": "Slack User Made Owner",
}
def rule(event):
return event.get("action") in USER_PRIV_ESC_ACTIONS
def title(event):
# This is the user taking the action.
actor_username = event.deep_get("actor", "user", "name", default="<unknown-actor>")
actor_email = event.deep_get("actor", "user", "email", default="<unknown-email>")
# This is the user the action is taken on.
entity_username = event.deep_get("entity", "user", "name", default="<unknown-actor>")
entity_email = event.deep_get("entity", "user", "email", default="<unknown-email>")
action = event.get("action")
if action == "owner_transferred":
return f"{USER_PRIV_ESC_ACTIONS[action]} from {actor_username} ({actor_email})"
if action == "permissions_assigned":
return f"{USER_PRIV_ESC_ACTIONS[action]} {actor_username} ({actor_email})"
if action == "role_change_to_admin":
return f"{USER_PRIV_ESC_ACTIONS[action]} {entity_username} ({entity_email})"
if action == "role_change_to_owner":
return f"{USER_PRIV_ESC_ACTIONS[action]} {entity_username} ({entity_email})"
return f"Slack User Privilege Escalation event {action} on {entity_username} ({entity_email})"
def severity(event):
# Downgrade severity for users assigned permissions
if event.get("action") == "permissions_assigned":
return "Medium"
return "Critical"
def alert_context(event):
return slack_alert_context(event)
Rule specification
AnalysisType: rule
Filename: slack_user_privilege_escalation.py
RuleID: "Slack.AuditLogs.UserPrivilegeEscalation"
DisplayName: "Slack User Privilege Escalation"
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Privilege Escalation
- Account Manipulation
- Additional Cloud Roles
Reports:
MITRE ATT&CK:
- TA0004:T1098.003
Severity: High
Description: Detects when a Slack user gains escalated privileges
Reference: https://slack.com/intl/en-gb/help/articles/201314026-Permissions-by-role-in-Slack
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionis one ofowner_transferred,permissions_assigned,role_change_to_admin,role_change_to_owner
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | in |
| field:"action" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
action | |
name | entity.user.name |
email | entity.user.email |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "owner_transferred",
"actor": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "A012B3CDEFG",
"name": "username",
"team": "T01234N56GB"
}
},
"context": {
"ip_address": "1.2.3.4",
"location": {
"domain": "test-workspace",
"id": "T01234N56GB",
"name": "test-workspace",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36"
},
"entity": {
"type": "user",
"user": {
"email": "user@example.com",
"id": "W012J3FEWAU",
"name": "primary-owner",
"team": "T01234N56GB"
}
}
}
Slack User Privileges Changed to User
#Detects when a Slack account is changed to User from an elevated role.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Impact |
Detection logic
from panther_slack_helpers import slack_alert_context
def rule(event):
return event.get("action") == "role_change_to_user"
def title(event):
username = event.deep_get("entity", "user", "name", default="<unknown-entity>")
email = event.deep_get("entity", "user", "email", default="<unknown-email>")
return f"Slack {username}'s ({email}) role changed to User"
def alert_context(event):
return slack_alert_context(event)
Rule specification
AnalysisType: rule
Filename: slack_privilege_changed_to_user.py
RuleID: Slack.AuditLogs.UserPrivilegeChangedToUser
DisplayName: Slack User Privileges Changed to User
Enabled: true
LogTypes:
- Slack.AuditLogs
Tags:
- Slack
- Impact
- Account Access Removal
Reports:
MITRE ATT&CK:
- TA0040:T1531
Severity: Medium
Description: Detects when a Slack account is changed to User from an elevated role.
Reference: https://slack.com/intl/en-gb/help/articles/360018112273-Types-of-roles-in-Slack
DedupPeriodMinutes: 60
Threshold: 1
SummaryAttributes:
- p_any_ip_addresses
- p_any_emails
Stages and Predicates
Fires on Slack.AuditLogs events when the condition below holds.
Condition
actionisrole_change_to_user
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
action | eq |
| field:"action" kind:eq value:"role_change_to_user" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
actor-name | actor.user.name |
actor-email | actor.user.email |
actor-ip | context.ip_address |
user-agent | context.ua |
name | entity.user.name |
email | entity.user.email |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"action": "role_change_to_user",
"actor": {
"type": "user",
"user": {
"email": "slack-enterprise-example@example.io",
"id": "W015MH5MPGE",
"name": "primary-owner",
"team": "T017E0M3CQ4"
}
},
"context": {
"ip_address": "12.12.12.12",
"location": {
"domain": "example-workspace-domain",
"id": "T017E0M3CQ4",
"name": "example-workspace",
"type": "workspace"
},
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36"
},
"date_create": "2023-02-24 18:34:18",
"entity": {
"type": "user",
"user": {
"email": "example-account@example.com",
"id": "U04R70MM40K",
"name": "Example Account",
"team": "T017E0M3CQ4"
}
},
"id": "4c248a02-119c-4f76-ba5d-a96767d45be8"
}