Detection rules › Panther
Panther rules: dropbox
Dropbox Admin sign-in-as Session
#Alerts when an admin starts a sign-in-as session.
Detection logic
def rule(event):
return event.deep_get("event_type", "_tag", default="") == "sign_in_as_session_start"
def title(event):
actor = event.deep_get("actor", "admin", "email", default="<ACTOR_NOT_FOUND>")
target = event.deep_get("context", "email", default="<TARGET_NOT_FOUND>")
return f"Dropbox: Admin [{actor}] started a sign-in-as session as user [{target}]."
Rule specification
AnalysisType: rule
Description: "Alerts when an admin starts a sign-in-as session."
DisplayName: "Dropbox Admin sign-in-as Session"
Enabled: true
Filename: dropbox_admin_sign_in_as_session.py
Reference: https://help.dropbox.com/security/sign-in-as-user
Severity: Medium
DedupPeriodMinutes: 60
LogTypes:
- Dropbox.TeamEvent
RuleID: "Dropbox.Admin.sign.in.as.Session"
Threshold: 1
Stages and Predicates
Fires on Dropbox.TeamEvent events when the condition below holds.
Condition
event_type._tagissign_in_as_session_start
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event_type._tag | eq |
| field:"event_type._tag" kind:eq value:"sign_in_as_session_start" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | actor.admin.email |
email | context.email |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"actor": {
"_tag": "admin",
"admin": {
"_tag": "team_member",
"account_id": "dbid:ABCDEFGHIJKLMONOPASQRST",
"display_name": "senvironment",
"email": "adminuser@company.com",
"team_member_id": "dbmid:AAAwgqIsigitfNAUAhsbxzQKtIPBM7uGAgg"
}
},
"context": {
"_tag": "team_member",
"account_id": "dbid:AAACjvKy90uezyOiLRadIuCy66dK5d1vGGw",
"display_name": "Alice Bob",
"email": "alice.bob@company.com",
"team_member_id": "dbmid:AADSERs2cAsByYt8yQEDU4_qdNQiSdxgCl8"
},
"details": {
".tag": "sign_in_as_session_start_details"
},
"event_category": {
"_tag": "logins"
},
"event_type": {
"_tag": "sign_in_as_session_start",
"description": "Started admin sign-in-as session"
},
"involve_non_team_member": false,
"origin": {
"access_method": {
".tag": "end_user",
"end_user": {
".tag": "web",
"session_id": "dbwsid:89515573818299775425117508904073133360"
}
},
"geo_location": {
"city": "Austin",
"country": "US",
"ip_address": "1.2.3.4",
"region": "Texas"
}
},
"p_any_emails": [
"alice.bob@company.com",
"adminuser@company.com"
],
"p_any_ip_addresses": [
"1.2.3.4"
],
"p_any_usernames": [
"Alice Bob",
"senvironment"
],
"p_event_time": "2023-04-19 19:20:02",
"p_log_type": "Dropbox.TeamEvent",
"p_parse_time": "2023-04-19 19:21:46.802",
"p_row_id": "ca46ddc6518083b5a4cbbed517dc8f02",
"p_schema_version": 0,
"p_source_id": "b09c205e-42af-4933-8b18-b910985eb7fb",
"p_source_label": "dropbox1",
"timestamp": "2023-04-19 19:20:02"
}
Dropbox Document/Folder Ownership Transfer
#Dropbox ownership of a document or folder has been transferred.
Detection logic
def rule(event):
return "Transferred ownership " in event.deep_get("event_type", "description", default="")
def title(event):
actor = event.deep_get("actor", "user", "email", default="<EMAIL_NOT_FOUND>")
previous_owner = event.deep_get(
"details", "previous_owner_email", default="<PREVIOUS_OWNER_NOT_FOUND>"
)
new_owner = event.deep_get("details", "new_owner_email", default="<NEW_OWNER_NOT_FOUND>")
assets = event.get("assets", [{}])
asset = [a.get("display_name", "<ASSET_NOT_FOUND>") for a in assets]
return (
f"Dropbox: [{actor}] transferred ownership of [{asset}]"
f"from [{previous_owner}] to [{new_owner}]."
)
def severity(event):
new_owner_domain = event.deep_get("details", "new_owner_email", default="@").split("@")[-1]
previous_owner_domain = event.deep_get("details", "previous_owner_email", default="@").split(
"@"
)[-1]
if new_owner_domain != previous_owner_domain:
return "DEFAULT"
return "LOW"
Rule specification
AnalysisType: rule
Description: "Dropbox ownership of a document or folder has been transferred."
DisplayName: "Dropbox Document/Folder Ownership Transfer"
Enabled: true
Filename: dropbox_ownership_transfer.py
Reference: https://help.dropbox.com/share/owner
Severity: Medium
DedupPeriodMinutes: 60
LogTypes:
- Dropbox.TeamEvent
RuleID: "Dropbox.Ownership.Transfer"
Threshold: 1
Stages and Predicates
Fires on Dropbox.TeamEvent events when the condition below holds.
Condition
event_type.descriptioncontains"Transferred ownership "
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event_type.description | contains |
| field:"event_type.description" kind:contains value:"Transferred ownership " |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | actor.user.email |
previous_owner_email | details.previous_owner_email |
new_owner_email | details.new_owner_email |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"actor": {
"_tag": "user",
"user": {
"_tag": "team_member",
"account_id": "dbid:AAAAAAAAAAAAAAAAAAAAA",
"display_name": "Alice Bob",
"email": "alice.bob@company.io",
"team_member_id": "dbmid:BBBBBBBBBBBBBBBBBBBBBB"
}
},
"assets": [
{
".tag": "folder",
"display_name": "test1",
"path": {
"contextual": "/Alice Bob/test1",
"namespace_relative": {
"is_shared_namespace": true,
"ns_id": "12345"
}
}
}
],
"context": {
"_tag": "team_member",
"account_id": "dbid:AAAAAAAAAAAAAAAAAAAAA",
"display_name": "Alice Bob",
"email": "alice.bob@company.io",
"team_member_id": "dbmid:BBBBBBBBBBBBBBBBBBBBBB"
},
"details": {
".tag": "shared_folder_transfer_ownership_details",
"new_owner_email": "david.davidson@company.io",
"previous_owner_email": "alice.bob@company.io"
},
"event_category": {
"_tag": "sharing"
},
"event_type": {
"_tag": "shared_folder_transfer_ownership",
"description": "Transferred ownership of shared folder to another member"
},
"involve_non_team_member": false,
"origin": {
"access_method": {
".tag": "end_user",
"end_user": {
".tag": "web",
"session_id": "dbwsid:237034608707419186011941491025532848312"
}
},
"geo_location": {
"city": "Austin",
"country": "US",
"ip_address": "1.2.3.4",
"region": "Texas"
}
},
"p_any_emails": [
"alice.bob@company.io",
"david.davidson@company.io"
],
"p_any_ip_addresses": [
"1.2.3.4"
],
"p_any_usernames": [
"Alice Bob",
"company"
],
"p_event_time": "2023-04-18 18:54:15",
"p_log_type": "Dropbox.TeamEvent",
"p_parse_time": "2023-04-18 18:56:47.418",
"p_row_id": "0eb86fcfca9bb1cdce9defd217e1cd04",
"p_schema_version": 0,
"p_source_id": "b09c205e-42af-4933-8b18-b910985eb7fb",
"p_source_label": "dropbox1",
"participants": [
{
"user": {
"_tag": "team_member",
"account_id": "dbid:ABCD",
"display_name": "company",
"email": "david.davidson@company.io",
"team_member_id": "dbmid:DEFG"
}
}
],
"timestamp": "2023-04-18 18:54:15"
}
Dropbox Linked Team Application Added
#An application was linked to your Dropbox Account
Detection logic
def rule(event):
return all(
[
event.deep_get("event_type", "_tag", default="") == "app_link_team",
event.deep_get("event_type", "description", default="") == "Linked app for team",
]
)
def severity(event):
# Anything involving non-team members should be High
if event.get("involve_non_team_member", False):
return "High"
return "Low"
def get_actor_type():
return (
# Admin who performed the action
"admin",
# Anonymous actor
"anonymous",
# Application that performed the action
"app",
# Action performed by Dropbox
"dropbox",
# Action performed by reseller
"reseller",
# User who performed the action
"user",
)
def title(event):
# This will be one of the types returned by get_actor_type;
# find the intersection and use that for the key
actor_key = set(tuple(event.get("actor", {}).keys())).intersection(get_actor_type())
if len(actor_key) == 1:
display_name = event.deep_get(
"actor", tuple(actor_key)[0], "display_name", default="<Unknown>"
)
# Explicitly use "<Unknown>" if we find any length of keys != 1
else:
display_name = "<Unknown>"
return f"Dropbox Team Member Linked App by [{display_name}]"
def user_details(event):
details = {}
for actor_key, actor_value in event.get("actor", {}).items():
if actor_key == "_tag":
continue
for user_key, user_info in actor_value.items():
if user_key in ("_tag", "display_name"):
continue
details[user_key] = user_info
return details
def alert_context(event):
additional_user_details = user_details(event)
return {
"additional_user_details": additional_user_details,
"app_display_name": event.deep_get(
"details", "app_info", "display_name", default="<Unknown app display name>"
),
"ip_address": event.deep_get(
"origin", "geo_location", "ip_address", default="<Unknown IP address>"
),
"request_id": event.deep_get(
"origin", "access_method", "request_id", default="<Unknown request ID>"
),
}
Rule specification
AnalysisType: rule
Description: An application was linked to your Dropbox Account
DisplayName: "Dropbox Linked Team Application Added"
Enabled: true
Filename: dropbox_linked_team_application_added.py
Reference: https://help.dropbox.com/integrations/app-integrations
Runbook: >
Ensure that the application is valid and not malicious. Verify that this is expected. If not, determine other actions taken by this user recently and reach out to the user.
If the event involved a non-team member, consider disabling the user's access while investigating.
Severity: Low
Tags:
- dropbox
DedupPeriodMinutes: 60
LogTypes:
- Dropbox.TeamEvent
RuleID: "Dropbox.Linked.Team.Application.Added"
Threshold: 1
Stages and Predicates
Fires on Dropbox.TeamEvent events when all of the conditions below hold.
Condition
event_type._tagisapp_link_teamevent_type.descriptionisLinked app for team
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event_type._tag | eq |
| field:"event_type._tag" kind:eq value:"app_link_team" |
event_type.description | eq |
| field:"event_type.description" kind:eq value:"Linked app for team" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
app_display_name | details.app_info.display_name |
ip_address | origin.geo_location.ip_address |
request_id | origin.access_method.request_id |
Response runbook
Ensure that the application is valid and not malicious. Verify that this is expected. If not, determine other actions taken by this user recently and reach out to the user. If the event involved a non-team member, consider disabling the user's access while investigating.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"actor": {
"_tag": "user",
"user": {
"_tag": "team_member",
"account_id": "dbid:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"display_name": "user_name",
"email": "user@domain.com",
"team_member_id": "dbmid:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
},
"context": {
"_tag": "team"
},
"details": {
".tag": "app_link_team_details",
"app_info": {
".tag": "team_linked_app",
"app_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"display_name": "dropbox-app-name"
}
},
"event_category": {
"_tag": "apps"
},
"event_type": {
"_tag": "app_link_team",
"description": "Linked app for team"
},
"involve_non_team_member": false,
"origin": {
"access_method": {
".tag": "api",
"request_id": "dbarod:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
"geo_location": {
"city": "Los Angeles",
"country": "US",
"ip_address": "1.2.3.4",
"region": "California"
}
},
"timestamp": "2023-02-16 20:39:34"
}
Dropbox Many Deletes
#Detects when a dropbox user deletes many documents.
Detection logic
def rule(_):
return True
def title(event):
return (
f"Dropbox: [{event.get('user', '<user_not_found>')}] "
f"has deleted [{event.get('delete_count', '<count_not_found>')}] "
"documents from Dropbox."
)
def alert_context(event):
return event.to_dict()
Rule specification
AnalysisType: scheduled_rule
Description: Detects when a dropbox user deletes many documents.
DisplayName: "Dropbox Many Deletes"
Enabled: false
Status: Deprecated
Filename: dropbox_many_deletes.py
Reference: https://help.dropbox.com/delete-restore/delete-files
Severity: Medium
DedupPeriodMinutes: 60
RuleID: "Dropbox.Many.Deletes"
Threshold: 1
ScheduledQueries:
- Dropbox Many Deletes
Tags:
- Configuration Required
Stages and Predicates
Rule logic
This rule alerts on rows returned by its scheduled query Dropbox Many Deletes; its Python module (Detection logic above) shapes the alert rather than filtering.
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
user |
delete_count |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"delete_count": 4,
"deleted_files": [
"/test/test2.txt",
"/test/test3.txt",
"/test/test5.txt",
"/test/test4.txt"
],
"t_s": "2023-04-19 19:00:00",
"user": "alice.bob@company.com"
}
Dropbox Many Deletes
#Dropbox Many Deletes
Rule specification
AnalysisType: scheduled_query
Description: Dropbox Many Deletes
Enabled: false
SnowflakeQuery: |-
SELECT
actor:user:email AS user,
ARRAY_AGG( DISTINCT assets[0]:path:contextual) AS deleted_files,
ARRAY_SIZE(deleted_files) as delete_count,
TIME_SLICE(p_event_time, 60, 'minute') as t_s
FROM panther_logs.public.dropbox_teamevent
WHERE p_occurs_since('1 day')
AND event_type:_tag = 'file_delete'
GROUP BY actor:user:email, t_s
HAVING delete_count > 2
ORDER BY delete_count DESC
DatabricksQuery: |-
SELECT
actor:user:email AS user,
COLLECT_SET(assets[0]:path:contextual) AS deleted_files,
SIZE(COLLECT_SET(assets[0]:path:contextual)) AS delete_count,
DATE_TRUNC('hour', p_event_time) AS t_s
FROM panther_logs.dropbox_teamevent
WHERE p_occurs_since('1 day')
AND event_type:_tag = 'file_delete'
GROUP BY actor:user:email, DATE_TRUNC('hour', p_event_time)
HAVING delete_count > 2
ORDER BY delete_count DESC
QueryName: "Dropbox Many Deletes"
Schedule:
RateMinutes: 1440
TimeoutMinutes: 3
Stages and Predicates
Stage 1: source
Stage 2: filter
event_type:_tagisfile_delete
Stage 3: having
delete_countis greater than2
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
delete_count | gt |
| field:"delete_count" kind:gt value:"2" |
event_type:_tag | eq |
| field:"event_type:_tag" kind:eq value:"file_delete" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
user | actor:user:email |
deleted_files | ARRAY_AGG ( DISTINCT assets [ 0 ] : path:contextual ) |
delete_count | ARRAY_SIZE ( deleted_files ) |
t_s | TIME_SLICE ( p_event_time , 60 , 'minute' ) |
Dropbox Many Deletes
#Detects when a Dropbox user deletes more than 10 distinct files within 60 minutes. This may indicate accidental or malicious bulk deletion of team files. The threshold should be tuned to your environment.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Impact |
Detection logic
def rule(event):
return event.deep_get("event_type", "_tag", default="") == "file_delete"
def title(event):
user = event.deep_get("actor", "user", "email", default="<UNKNOWN_USER>")
return f"Dropbox: User [{user}] deleted many files"
def dedup(event):
return event.deep_get("actor", "user", "email", default="")
def unique(event):
assets = event.get("assets", [])
if assets:
return assets[0].get("path", {}).get("contextual") or None
return None
def severity(event):
if event.get("involve_non_team_member", False):
return "HIGH"
return "DEFAULT"
def alert_context(event):
assets = event.get("assets", [])
file_path = assets[0].get("path", {}).get("contextual", "") if assets else ""
return {
"user": event.deep_get("actor", "user", "email"),
"file_path": file_path,
"involve_non_team_member": event.get("involve_non_team_member"),
"ip_address": event.deep_get("origin", "geo_location", "ip_address"),
}
Rule specification
AnalysisType: rule
Filename: dropbox_many_deletes.py
RuleID: "Dropbox.BulkDeletes"
DisplayName: "Dropbox Many Deletes"
Status: Experimental
Enabled: false
Severity: Medium
DedupPeriodMinutes: 60
Threshold: 11
LogTypes:
- Dropbox.TeamEvent
Description: >
Detects when a Dropbox user deletes more than 10 distinct files within 60 minutes.
This may indicate accidental or malicious bulk deletion of team files.
The threshold should be tuned to your environment.
Reference: https://help.dropbox.com/delete-restore/delete-files
Reports:
MITRE ATT&CK:
- TA0040:T1485
Tags:
- Dropbox
- Impact
- Data Destruction
Runbook: |
1. Query Dropbox.TeamEvent for all file_delete events by actor:user:email in the 2 hours around this alert to identify the full list of assets[0]:path:contextual values deleted
2. Check involve_non_team_member and origin:geo_location:ip_address to determine if the deletions originated from an unexpected user or location, and assess whether the deleted files were shared with external parties
3. Search for other suspicious activity by this user in the past 24 hours, including bulk downloads prior to deletion, external sharing events, or login anomalies
Stages and Predicates
Fires on Dropbox.TeamEvent events when the condition below holds.
Condition
event_type._tagisfile_delete
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event_type._tag | eq |
| field:"event_type._tag" kind:eq value:"file_delete" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
user | actor.user.email |
involve_non_team_member | |
ip_address | origin.geo_location.ip_address |
Response runbook
1. Query Dropbox.TeamEvent for all file_delete events by actor:user:email in the 2 hours around this alert to identify the full list of assets[0]:path:contextual values deleted
2. Check involve_non_team_member and origin:geo_location:ip_address to determine if the deletions originated from an unexpected user or location, and assess whether the deleted files were shared with external parties
3. Search for other suspicious activity by this user in the past 24 hours, including bulk downloads prior to deletion, external sharing events, or login anomalies
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"actor": {
"user": {
"_tag": "team_member",
"email": "alice@company.com"
}
},
"assets": [
{
"path": {
"contextual": "/alice/report.pdf"
}
}
],
"event_type": {
"_tag": "file_delete"
},
"involve_non_team_member": false,
"origin": {
"geo_location": {
"ip_address": "1.2.3.4"
}
}
}
Dropbox Many Downloads
#Detects when a dropbox user downloads many documents.
Detection logic
def rule(_):
return True
def title(event):
return (
f"Dropbox: [{event.get('user', '<user_not_found>')}] "
f"has downloaded [{event.get('download_count', '<count_not_found>')}] "
"documents from Dropbox."
)
def alert_context(event):
return event.to_dict()
Rule specification
AnalysisType: scheduled_rule
Description: Detects when a dropbox user downloads many documents.
DisplayName: "Dropbox Many Downloads"
Enabled: false
Status: Deprecated
Filename: dropbox_many_downloads.py
Reference: https://learn.dropbox.com/video-library/upload-and-download-files
Severity: Medium
DedupPeriodMinutes: 60
RuleID: "Dropbox.Many.Downloads"
Threshold: 1
ScheduledQueries:
- Dropbox Many Downloads
Tags:
- Configuration Required
Stages and Predicates
Rule logic
This rule alerts on rows returned by its scheduled query Dropbox Many Downloads; its Python module (Detection logic above) shapes the alert rather than filtering.
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
user |
download_count |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"download_count": 4,
"downloaded_files": [
"/test/test2.txt",
"/test/test3.txt",
"/test/test5.txt",
"/test/test4.txt"
],
"t_s": "2023-04-19 19:00:00",
"user": "alice.bob@company.com"
}
Dropbox Many Downloads
#Dropbox Many Downloads
Rule specification
AnalysisType: scheduled_query
Description: Dropbox Many Downloads
Enabled: false
SnowflakeQuery: |-
SELECT
actor:user:email AS user,
ARRAY_AGG( DISTINCT assets[0]:path:contextual) AS downloaded_files,
ARRAY_SIZE(downloaded_files) as download_count,
TIME_SLICE(p_event_time, 60, 'minute') as t_s
FROM panther_logs.public.dropbox_teamevent
WHERE p_occurs_since('1 day')
AND event_type:_tag = 'file_download'
GROUP BY actor:user:email, t_s
HAVING download_count > 10
ORDER BY download_count DESC
DatabricksQuery: |-
SELECT
actor:user:email AS user,
COLLECT_SET(assets[0]:path:contextual) AS downloaded_files,
SIZE(COLLECT_SET(assets[0]:path:contextual)) AS download_count,
DATE_TRUNC('hour', p_event_time) AS t_s
FROM panther_logs.dropbox_teamevent
WHERE p_occurs_since('1 day')
AND event_type:_tag = 'file_download'
GROUP BY actor:user:email, DATE_TRUNC('hour', p_event_time)
HAVING download_count > 10
ORDER BY download_count DESC
QueryName: "Dropbox Many Downloads"
Schedule:
RateMinutes: 1440
TimeoutMinutes: 3
Stages and Predicates
Stage 1: source
Stage 2: filter
event_type:_tagisfile_download
Stage 3: having
download_countis greater than10
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
download_count | gt |
| field:"download_count" kind:gt value:"10" |
event_type:_tag | eq |
| field:"event_type:_tag" kind:eq value:"file_download" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
user | actor:user:email |
downloaded_files | ARRAY_AGG ( DISTINCT assets [ 0 ] : path:contextual ) |
download_count | ARRAY_SIZE ( downloaded_files ) |
t_s | TIME_SLICE ( p_event_time , 60 , 'minute' ) |
Dropbox Many Downloads
#Detects when a Dropbox user downloads more than 10 distinct files within 60 minutes. This may indicate data exfiltration or unauthorized bulk access to team files. The threshold should be tuned to your environment.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Exfiltration |
Detection logic
def rule(event):
return event.deep_get("event_type", "_tag", default="") == "file_download"
def title(event):
user = event.deep_get("actor", "user", "email", default="<UNKNOWN_USER>")
return f"Dropbox: User [{user}] downloaded many files"
def dedup(event):
return event.deep_get("actor", "user", "email", default="")
def unique(event):
assets = event.get("assets", [])
if assets:
return assets[0].get("path", {}).get("contextual") or None
return None
def severity(event):
if event.get("involve_non_team_member", False):
return "HIGH"
return "DEFAULT"
def alert_context(event):
assets = event.get("assets", [])
file_path = assets[0].get("path", {}).get("contextual", "") if assets else ""
return {
"user": event.deep_get("actor", "user", "email"),
"file_path": file_path,
"involve_non_team_member": event.get("involve_non_team_member"),
"ip_address": event.deep_get("origin", "geo_location", "ip_address"),
}
Rule specification
AnalysisType: rule
Filename: dropbox_many_downloads.py
RuleID: "Dropbox.BulkDownloads"
DisplayName: "Dropbox Many Downloads"
Status: Experimental
Enabled: false
Severity: Medium
DedupPeriodMinutes: 60
Threshold: 11
LogTypes:
- Dropbox.TeamEvent
Description: >
Detects when a Dropbox user downloads more than 10 distinct files within 60 minutes.
This may indicate data exfiltration or unauthorized bulk access to team files.
The threshold should be tuned to your environment.
Reference: https://learn.dropbox.com/video-library/upload-and-download-files
Reports:
MITRE ATT&CK:
- TA0010:T1567
Tags:
- Dropbox
- Exfiltration
Runbook: |
1. Query Dropbox.TeamEvent for all file_download events by actor:user:email in the 2 hours around this alert to identify the full list of assets[0]:path:contextual values downloaded
2. Check involve_non_team_member and the origin:geo_location:ip_address to determine if the downloads originated from an external user or an unexpected location
3. Search for other suspicious activity by this user in the past 24 hours, including external sharing events, ownership transfers, or logins from new devices or locations
Stages and Predicates
Fires on Dropbox.TeamEvent events when the condition below holds.
Condition
event_type._tagisfile_download
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event_type._tag | eq |
| field:"event_type._tag" kind:eq value:"file_download" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
user | actor.user.email |
involve_non_team_member | |
ip_address | origin.geo_location.ip_address |
Response runbook
1. Query Dropbox.TeamEvent for all file_download events by actor:user:email in the 2 hours around this alert to identify the full list of assets[0]:path:contextual values downloaded
2. Check involve_non_team_member and the origin:geo_location:ip_address to determine if the downloads originated from an external user or an unexpected location
3. Search for other suspicious activity by this user in the past 24 hours, including external sharing events, ownership transfers, or logins from new devices or locations
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"actor": {
"user": {
"_tag": "team_member",
"email": "alice@company.com"
}
},
"assets": [
{
"path": {
"contextual": "/alice/report.pdf"
}
}
],
"event_type": {
"_tag": "file_download"
},
"involve_non_team_member": false,
"origin": {
"geo_location": {
"ip_address": "1.2.3.4"
}
}
}
Dropbox User Disabled 2FA
#Dropbox user has disabled 2fa login
Detection logic
def rule(event):
return all(
[
event.deep_get("details", ".tag", default="") == "tfa_change_status_details",
event.deep_get("details", "new_value", ".tag") == "disabled",
]
)
def title(event):
actor = event.deep_get("actor", "user", "email", default="<EMAIL_NOT_FOUND>")
target = event.deep_get("context", "email", default="<TARGET_NOT_FOUND>")
return f"Dropbox: [{actor}] disabled 2FA for [{target}]."
Rule specification
AnalysisType: rule
Description: Dropbox user has disabled 2fa login
DisplayName: "Dropbox User Disabled 2FA"
Enabled: true
Filename: dropbox_user_disabled_2fa.py
Reference: https://help.dropbox.com/account-access/enable-two-step-verification
Severity: Low
DedupPeriodMinutes: 60
LogTypes:
- Dropbox.TeamEvent
RuleID: "Dropbox.User.Disabled.2FA"
Threshold: 1
Stages and Predicates
Fires on Dropbox.TeamEvent events when all of the conditions below hold.
Condition
details..tagistfa_change_status_detailsdetails.new_value..tagisdisabled
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
details..tag | eq |
| field:"details..tag" kind:eq value:"tfa_change_status_details" |
details.new_value..tag | eq |
| field:"details.new_value..tag" kind:eq value:"disabled" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | actor.user.email |
email | context.email |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"actor": {
"_tag": "user",
"user": {
"_tag": "team_member",
"account_id": "dbid:AAAAAAAAAAAAAAAA",
"display_name": "Alice Bob",
"email": "alice.bob@company.io",
"team_member_id": "dbmid:AABBBBBBBBBBBBBBBBBBBBBBB"
}
},
"context": {
"_tag": "team_member",
"account_id": "dbid:AAAAAAAAAAAAAAAA",
"display_name": "Alice Bob",
"email": "alice.bob@company.io",
"team_member_id": "dbmid:AABBBBBBBBBBBBBBBBBBBBBBB"
},
"details": {
".tag": "tfa_change_status_details",
"new_value": {
".tag": "disabled"
},
"previous_value": {
".tag": "authenticator"
},
"used_rescue_code": true
},
"event_category": {
"_tag": "tfa"
},
"event_type": {
"_tag": "tfa_change_status",
"description": "Enabled/disabled/changed two-step verification setting"
},
"involve_non_team_member": false,
"origin": {
"access_method": {
".tag": "end_user",
"end_user": {
".tag": "web",
"session_id": "dbwsid:237034608707419186011941491025532848312"
}
},
"geo_location": {
"city": "Austin",
"country": "US",
"ip_address": "1.2.3.4",
"region": "Texas"
}
},
"p_any_emails": [
"alice.bob@company.io"
],
"p_any_ip_addresses": [
"1.2.3.4"
],
"p_any_usernames": [
"Alice Bob"
],
"p_event_time": "2023-04-18 18:16:27",
"p_log_type": "Dropbox.TeamEvent",
"p_parse_time": "2023-04-18 18:18:46.808",
"p_row_id": "0eb86fcfca9bb1cdce9defd217b8ac03",
"p_schema_version": 0,
"p_source_id": "b09c205e-42af-4933-8b18-b910985eb7fb",
"p_source_label": "dropbox1",
"timestamp": "2023-04-18 18:16:27"
}