Skip to content

Commit

Permalink
Adding Some Snowflake Behavioral/Anomaly Scheduled Queries (#1408)
Browse files Browse the repository at this point in the history
Co-authored-by: Ariel Ropek <[email protected]>
  • Loading branch information
ben-githubs and arielkr256 authored Nov 12, 2024
1 parent ac1ad37 commit 81a1f7e
Show file tree
Hide file tree
Showing 11 changed files with 310 additions and 0 deletions.
8 changes: 8 additions & 0 deletions packs/snowflake_streaming.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,24 @@ PackDefinition:
- Snowflake.PotentialBruteForceSuccess
# Helpers
- panther_snowflake_helpers
# Queries
- Snowflake Attempted Login With Disabled User
- Snowflake User Daily Query Volume Spike
- Snowflake User Daily Query Volume Spike - Threat Hunting
- Suspicious Snowflake Sessions - Unusual Application
# Rules
- Snowflake.Stream.AccountAdminGranted
- Snowflake.Stream.AttemptedLoginByDisabledUser
- Snowflake.Stream.BruteForceByIp
- Snowflake.Stream.BruteForceByUsername
- Snowflake.Stream.ExternalShares
- Snowflake.Stream.FileDownloaded
- Snowflake.Stream.LoginSuccess
- Snowflake.Stream.LoginWithoutMFA
- Snowflake.Stream.PublicRoleGrant
- Snowflake.Stream.SuspiciousSession.UnusualApp
- Snowflake.Stream.TableCopiedIntoStage
- Snowflake.Stream.TempStageCreated
- Snowflake.Stream.UserCreated
- Snowflake.Stream.UserDailyQueryVolumeSpike
- Snowflake.Stream.UserEnabled
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
def rule(_):
return True


def title(event):
source = event.get("p_source_label", "<UNKNOWN SOURCE>")
username = event.get("USER_NAME", "<UNKNOWN USER>")
return f"{source}: Attempted signin by disabled user {username}"


def alert_context(event):
return event.get("user")
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
AnalysisType: scheduled_rule
Filename: snowflake_attempted_login_by_disabled_user.py
RuleID: "Snowflake.Stream.AttemptedLoginByDisabledUser"
Enabled: true
ScheduledQueries:
- Snowflake Attempted Login With Disabled User
Severity: Low
Reports:
MITRE ATT&CK:
- TA0001:T1078.004
Description: >
Detects when a login is attempted by a disabled user account.
Tags:
- Snowflake
- Behavior Analysis
- Initial Access:Valid Accounts:Cloud Accounts
Tests:
- Name: Login by Disabled User
ExpectedResult: true
Log:
{
"p_source_label": "SF-Prod",
"user": {
"CREATED_ON": "2024-10-09 19:43:05.083000000",
"DEFAULT_ROLE": "PANTHER_AUDIT_VIEW_ROLE",
"DISABLED": true,
"DISPLAY_NAME":
"FORMER_ADMIN",
"EXT_AUTHN_DUO": false,
"HAS_MFA": false,
"HAS_PASSWORD": true,
"HAS_RSA_PUBLIC_KEY": false,
"LAST_SUCCESS_LOGIN": "2024-10-09 20:59:00.043000000",
"LOGIN_NAME": "FORMER_ADMIN",
"MUST_CHANGE_PASSWORD": false,
"NAME": "FORMER_ADMIN",
"OWNER": "ACCOUNTADMIN",
"SNOWFLAKE_LOCK": false,
"USER_ID": "51"
},
"USER_NAME": "FORMER_ADMIN"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
AnalysisType: scheduled_query
QueryName: Snowflake Attempted Login With Disabled User
Enabled: false
Description: >
Returns instances where a disabled user's login credentials were used in a login
attempt.
Tags:
- Snowflake
Query: |
with disabled_users as (
select DATA as USER from panther_logs.public.snowflake_users_variant
where USER:DISABLED = true
),
logins as (
select * from
panther_logs.public.snowflake_loginhistory
where p_occurs_since('24h', , p_parse_time)
)
select * from logins join disabled_users
on logins.USER_NAME = disabled_users.USER:NAME
Schedule:
RateMinutes: 1440
TimeoutMinutes: 2
16 changes: 16 additions & 0 deletions queries/snowflake_queries/snowflake_suspicious_session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
def rule(_):
return True


def title(event):
return f"{event.get('p_source_label', '<UNKNOWN SOURCE>')}: Suspicious Application Session"


def dedup(event):
return "-".join(
(
event.get("client_application", "<UNKNOWN APP>"),
event.get("client_os", "<UNKNOWN OS>"),
event.get("client_os_version", "<UNKNOWN VERSION>"),
)
)
30 changes: 30 additions & 0 deletions queries/snowflake_queries/snowflake_suspicious_session.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
AnalysisType: scheduled_rule
Filename: snowflake_suspicious_session.py
RuleID: "Snowflake.Stream.SuspiciousSession.UnusualApp"
DisplayName: Suspicious Snowflake Sessions - Unusual Application
Enabled: true
ScheduledQueries:
- "Suspicious Snowflake Sessions - Unusual Application"
Severity: Low
Reports:
MITRE ATT&CK:
- TA0001:T1078.004
Description: Detects unusual (non-common) applications and client characteristics
that have been used to connect to a Snowflake account
DedupPeriodMinutes: 1440
Tags:
- Snowflake
- Behavior Analysis
- Initial Access:Valid Accounts:Cloud Accounts
Tests:
- Name: New Session
ExpectedResult: true
Log:
{
"p_source_id": "26c3f2be-005e-443a-90cb-f623522f37a2",
"p_source_label": "SF Prod",
"client_application": "Snowflake Web App",
"first_seen": "2024-10-09 14:48:33.284",
"last_seen": "2024-10-09 15:01:13.492",
"n_sessions": 83
}
33 changes: 33 additions & 0 deletions queries/snowflake_queries/snowflake_suspicious_session_query.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
AnalysisType: scheduled_query
QueryName: Suspicious Snowflake Sessions - Unusual Application
Enabled: false
Description: This query can be used for the detection of unusual, non-common applications
and client characteristics that had been used to connect to the Snowflake account,
using a comparison to the previous usage baseline.
Schedule:
RateMinutes: 1320
TimeoutMinutes: 2
Tags:
- Snowflake
- Configuration Required
Query: |
-- Adjustments as follows:
-- adjust n_sessions threshold on line 18 as needed
-- adjust baseline lookback period on line 16 as desired
-- adust recent lookpack period on line 19 as desired
-- adjust scheduled query period to be 2 hrs shorter than the lookback window on line 19
select
CLIENT_ENVIRONMENT:APPLICATION as client_application,
CLIENT_ENVIRONMENT:OS as client_os,
CLIENT_ENVIRONMENT:OS_VERSION as client_os_version,
min(CREATED_ON) as first_seen,
max(CREATED_ON) as last_seen,
count(*) as n_sessions,
p_source_id,
p_source_label
from panther_logs.public.snowflake_sessions
where p_occurs_since(90d)
group by client_application, client_os, client_os_version, p_source_id, p_source_label
having n_sessions > 50
and first_seen > timeadd('day', -10, p_current_timestamp())
order by n_sessions desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
def rule(_):
return True


def title(event):
username = event.get("user_name", "<UNKNOWN USER>")
source = event.get("p_source_label", "<UNKNOWN SOURCE>")
return f"{source}: Abnormally large query volume from user {username}"
36 changes: 36 additions & 0 deletions queries/snowflake_queries/snowflake_user_query_volume_spike.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
AnalysisType: scheduled_rule
Filename: snowflake_user_query_volume_spike.py
RuleID: "Snowflake.Stream.UserDailyQueryVolumeSpike"
DisplayName: "Snowflake User Daily Query Volume Spike"
Enabled: true
ScheduledQueries:
- "Snowflake User Daily Query Volume Spike"
Severity: Low
Reports:
MITRE ATT&CK:
- TA0010:T1567
Description: >
Returns instances where a user's cumulative daily query volume is much larger than
normal. Could indicate exfiltration attempts.
Runbook: >
Review the user's query history for the past day. Identify any large queries
and determine if any data was accessed that shouldn't be.
Tags:
- Snowflake
- Behavior Analysis
- Exfiltration:Exfiltration Over Web Service
Tests:
- Name: High Query Volume
ExpectedResult: true
Log:
{
"p_source_id": "26c3f2be-005e-443a-90cb-f623522f37a2",
"p_source_label": "SF Prod",
"daily_bytes": 1376806.35210866,
"mean": 729289.7142857143,
"std_dev": 206110.99016770572,
"tend": "2024-10-21 22:09:13.47Z",
"tstart": "2024-10-20 22:09:13.47Z",
"USER_NAME": "ZEEBLE_BROMBUS",
"zscore": 3.141592,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
AnalysisType: scheduled_query
QueryName: "Snowflake User Daily Query Volume Spike"
Enabled: false
Description: >
Returns instances where a user's cumulative daily query volume is much larger than
normal. Could indicate exfiltration attempts.
Query: |
with t as (
select
dateadd('day', -1-seq4(), p_current_timestamp()) as tstart,
dateadd('day', -seq4(), p_current_timestamp()) as tend
from table(generator(rowcount => 90))
),
data as (
select
user_name,
end_time as t,
bytes_written_to_result as n_bytes,
p_source_id,
p_source_label
from panther_logs.public.snowflake_queryhistory
where p_occurs_since('90d', , end_time)
),
dimensions as (
select distinct user_name, p_source_id from data
),
axes as (
select * from t cross join dimensions
),
histogram as (
select
sum(data.n_bytes) as daily_bytes,
data.p_source_label,
axes.tstart,
axes.tend,
axes.user_name,
axes.p_source_id
from data join axes
on
p_occurs_between(axes.tstart, axes.tend, data, t)
and data.user_name = axes.user_name
and data.p_source_id = axes.p_source_id
group by (
axes.p_source_id,
data.p_source_label,
axes.tstart,
axes.tend,
axes.user_name
)
),
stats as (
select
avg(daily_bytes) as mean,
stddev(daily_bytes) as std_dev,
user_name,
p_source_id,
p_source_label
from histogram
group by
p_source_id,
p_source_label,
user_name
)
select
abs(histogram.daily_bytes - stats.mean) / COALESCE(NULLIF(stats.std_dev, 0), 1) as zscore,
histogram.*,
stats.mean,
stats.std_dev
from histogram join stats on
histogram.user_name = stats.user_name and
histogram.p_source_id = stats.p_source_id,
where p_occurs_since('12h', histogram, tend)
and zscore > 3
and histogram.daily_bytes > 1000000 -- Minimum 1MB threshold
Schedule:
CronExpression: "0 0 * * *"
TimeoutMinutes: 3
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
AnalysisType: saved_query
QueryName: "Snowflake User Daily Query Volume Spike - Threat Hunting"
Description: This query returns the most voluminous queries executed by a specific
user over the past 48 hours.
Tags:
- Snowflake
- Threat Hunting
Query: |-
-- pragma: template
-- Adjust 'username' and 'source_label' values as needed
{% set username = 'PANTHER_AUDIT_VIEW_USER' %}
{% set source_label = 'SF-Ben' %}
select
p_event_time,
BYTES_WRITTEN_TO_RESULT,
QUERY_TEXT,
QUERY_TAG,
EXECUTION_STATUS,
QUERY_ID,
from panther_logs.public.snowflake_queryhistory
where p_occurs_since('48h')
and USER_NAME = '{{username}}'
and p_source_label = '{{source_label}}'
order by BYTES_WRITTEN_TO_RESULT desc

0 comments on commit 81a1f7e

Please sign in to comment.