AnalysisType: rule
RuleID: "Snowflake.Stream.PasswordSpray"
DisplayName: "Snowflake Password Spray"
Filename: snowflake_stream_password_spray.py
Enabled: true
Status: Experimental
LogTypes:
- Snowflake.LoginHistory
Severity: Medium
Threshold: 5
DedupPeriodMinutes: 60
Description: >
Detects password spraying attacks against Snowflake by tracking the number of distinct
user accounts targeted by failed login attempts from the same source IP address within
a short timeframe. Unlike brute force against a single account, password spraying
distributes attempts across many accounts to evade lockout policies. This rule
complements Snowflake.Stream.BruteForceByIp (same IP, any accounts) and
Snowflake.PotentialBruteForceSuccess.Group (brute force followed by confirmed login).
Runbook: |
1. Review the distinct usernames targeted from the source IP and determine if any are privileged accounts (ACCOUNTADMIN, SYSADMIN, SECURITYADMIN) which would elevate the risk significantly
2. Query Snowflake LoginHistory for all events with IS_SUCCESS = 'YES' from the same CLIENT_IP within the past 60 minutes to determine if any spray attempt succeeded — the alert context shows only the last triggering username, not all targeted accounts
3. Check if the source IP belongs to known anonymization infrastructure (VPNs, Tor exit nodes, cloud provider ranges) and cross-reference against other Snowflake tenants if applicable
Reference: https://docs.snowflake.com/en/sql-reference/account-usage/login_history
Tags:
- Snowflake
- Credential Access
- Brute Force
Reports:
MITRE ATT&CK:
- TA0006:T1110.003 # Credential Access: Password Spraying
Tests:
- Name: Match - Failed Login
ExpectedResult: true
Log:
{
"p_event_time": "2024-10-08 14:38:46.061000000",
"p_log_type": "Snowflake.LoginHistory",
"CLIENT_IP": "1.2.3.4",
"EVENT_ID": "393754014361778",
"EVENT_TIMESTAMP": "2024-10-08 14:38:46.061000000",
"EVENT_TYPE": "LOGIN",
"FIRST_AUTHENTICATION_FACTOR": "PASSWORD",
"IS_SUCCESS": "NO",
"RELATED_EVENT_ID": "0",
"REPORTED_CLIENT_TYPE": "OTHER",
"REPORTED_CLIENT_VERSION": "1.11.1",
"USER_NAME": "ckent@dailyplanet.org"
}
- Name: Match - Failed Login Different User Same IP
ExpectedResult: true
Log:
{
"p_event_time": "2024-10-08 14:39:00.000000000",
"p_log_type": "Snowflake.LoginHistory",
"CLIENT_IP": "1.2.3.4",
"EVENT_ID": "393754014361779",
"EVENT_TIMESTAMP": "2024-10-08 14:39:00.000000000",
"EVENT_TYPE": "LOGIN",
"FIRST_AUTHENTICATION_FACTOR": "PASSWORD",
"IS_SUCCESS": "NO",
"RELATED_EVENT_ID": "0",
"REPORTED_CLIENT_TYPE": "OTHER",
"REPORTED_CLIENT_VERSION": "1.11.1",
"USER_NAME": "luthor@lexcorp.com"
}
- Name: No Match - Successful Login
ExpectedResult: false
Log:
{
"p_event_time": "2024-10-08 14:38:46.061000000",
"p_log_type": "Snowflake.LoginHistory",
"CLIENT_IP": "1.1.1.1",
"EVENT_ID": "393754014361780",
"EVENT_TIMESTAMP": "2024-10-08 14:38:46.061000000",
"EVENT_TYPE": "LOGIN",
"FIRST_AUTHENTICATION_FACTOR": "PASSWORD",
"IS_SUCCESS": "YES",
"RELATED_EVENT_ID": "0",
"REPORTED_CLIENT_TYPE": "OTHER",
"REPORTED_CLIENT_VERSION": "1.11.1",
"USER_NAME": "ckent@dailyplanet.org"
}
- Name: No Match - Overflow Failure Event
ExpectedResult: false
Log:
{
"p_event_time": "2024-11-15 00:12:24.288000000",
"p_log_type": "Snowflake.LoginHistory",
"CLIENT_IP": "0.0.0.0",
"ERROR_CODE": 390156,
"ERROR_MESSAGE": "OVERFLOW_FAILURE_EVENTS_ELIDED",
"EVENT_ID": "16592193114297018",
"EVENT_TIMESTAMP": "2024-11-15 00:12:24.288000000",
"EVENT_TYPE": "LOGIN",
"IS_SUCCESS": "NO",
"RELATED_EVENT_ID": "0",
"REPORTED_CLIENT_TYPE": "OTHER",
"REPORTED_CLIENT_VERSION": "0",
"USER_NAME": "luthor@lexcorp.com"
}
- Name: Match - JWT Key Mismatch Downgraded to Info
ExpectedResult: true
Log:
{
"p_event_time": "2024-10-08 14:38:46.061000000",
"p_log_type": "Snowflake.LoginHistory",
"CLIENT_IP": "1.2.3.4",
"ERROR_CODE": 394304,
"ERROR_MESSAGE": "JWT_TOKEN_INVALID_PUBLIC_KEY_FINGERPRINT_MISMATCH",
"EVENT_ID": "393754014361782",
"EVENT_TIMESTAMP": "2024-10-08 14:38:46.061000000",
"EVENT_TYPE": "LOGIN",
"FIRST_AUTHENTICATION_FACTOR": "PASSWORD",
"IS_SUCCESS": "NO",
"RELATED_EVENT_ID": "0",
"REPORTED_CLIENT_TYPE": "OTHER",
"REPORTED_CLIENT_VERSION": "1.11.1",
"USER_NAME": "brucewayne@waynecorp.com"
}
- Name: No Match - Non-Login Event
ExpectedResult: false
Log:
{
"p_event_time": "2024-10-08 14:38:46.061000000",
"p_log_type": "Snowflake.LoginHistory",
"CLIENT_IP": "1.2.3.4",
"EVENT_ID": "393754014361781",
"EVENT_TIMESTAMP": "2024-10-08 14:38:46.061000000",
"EVENT_TYPE": "LOGOUT",
"IS_SUCCESS": "YES",
"RELATED_EVENT_ID": "0",
"REPORTED_CLIENT_TYPE": "OTHER",
"REPORTED_CLIENT_VERSION": "1.11.1",
"USER_NAME": "ckent@dailyplanet.org"
}
# ------ paired body: snowflake_stream_password_spray.py ------
def rule(event):
return (
event.get("EVENT_TYPE") == "LOGIN"
and event.get("IS_SUCCESS") == "NO"
and event.get("ERROR_MESSAGE") != "OVERFLOW_FAILURE_EVENTS_ELIDED"
)
def unique(event):
return event.get("USER_NAME", "UNKNOWN_USER")
def dedup(event):
return event.get("CLIENT_IP", "UNKNOWN_IP")
def title(event):
client_ip = event.get("CLIENT_IP", "UNKNOWN_IP")
return f"[Snowflake] Password spray detected from IP [{client_ip}] targeting multiple accounts"
def severity(event):
# Downgrade JWT key mismatches to INFO as these are typically automation misconfiguration
if event.get("ERROR_MESSAGE") == "JWT_TOKEN_INVALID_PUBLIC_KEY_FINGERPRINT_MISMATCH":
return "INFO"
return "DEFAULT"
def alert_context(event):
return {
"client_ip": event.get("CLIENT_IP", "UNKNOWN_IP"),
"user_name": event.get("USER_NAME", "UNKNOWN_USER"),
"client_type": event.get("REPORTED_CLIENT_TYPE"),
"error_code": event.get("ERROR_CODE"),
"error_message": event.get("ERROR_MESSAGE"),
}