Potential Okta Credential Stuffing (Single Source)
Detects potential credential stuffing attacks where a single source IP attempts authentication against many Okta user accounts with minimal attempts per user, indicating the use of breached credential lists.
Elastic rule (View on GitHub)
1[metadata]
2creation_date = "2024/06/17"
3integration = ["okta"]
4maturity = "production"
5updated_date = "2026/09/18"
6
7[rule]
8author = ["Elastic"]
9description = """
10Detects potential credential stuffing attacks where a single source IP attempts authentication against many Okta
11user accounts with minimal attempts per user, indicating the use of breached credential lists.
12"""
13false_positives = [
14 "Corporate proxy or VPN exit nodes may aggregate traffic from multiple legitimate users.",
15 "Shared systems such as kiosks or conference room computers may have multiple users authenticating.",
16]
17from = "now-15m"
18language = "esql"
19license = "Elastic License v2"
20name = "Potential Okta Credential Stuffing (Single Source)"
21note = """## Triage and analysis
22
23### Investigating Potential Okta Credential Stuffing (Single Source)
24
25This rule identifies a single source IP attempting authentication against many user accounts with minimal attempts per user. This pattern indicates credential stuffing where attackers rapidly test breached username and password pairs.
26
27#### Possible investigation steps
28- Identify the source IP and determine if it belongs to known proxy, VPN, or cloud infrastructure.
29- Review the list of targeted user accounts and check if any authentications succeeded.
30- Examine the user agent strings for signs of automation or scripting tools.
31- Check if Okta flagged the source as a known threat or proxy.
32- Determine if any targeted accounts have elevated privileges or access to sensitive systems.
33- Review the geographic location and ASN of the source IP for anomalies.
34
35### False positive analysis
36- Corporate proxies or VPN exit nodes may aggregate traffic from multiple legitimate users.
37- Shared systems such as kiosks or conference room computers may have multiple users authenticating.
38- Legitimate SSO integrations may generate multiple authentication attempts from a single source.
39
40### Response and remediation
41- If attack is confirmed, block the source IP at the network perimeter.
42- Reset passwords for any accounts that may have been compromised.
43- Enable or strengthen MFA for targeted accounts.
44- Review Okta sign-on policies to add additional friction for suspicious authentication patterns.
45- If this is a known legitimate source, consider adding an exception for the IP or ASN.
46"""
47references = [
48 "https://support.okta.com/help/s/article/Troubleshooting-Distributed-Brute-Force-andor-Password-Spray-attacks-in-Okta",
49 "https://www.okta.com/identity-101/brute-force/",
50 "https://support.okta.com/help/s/article/How-does-the-Device-Token-work?language=en_US",
51 "https://developer.okta.com/docs/reference/api/event-types/",
52 "https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy",
53 "https://sec.okta.com/articles/2023/08/cross-tenant-impersonation-prevention-and-detection",
54 "https://www.okta.com/resources/whitepaper-how-adaptive-mfa-can-help-in-mitigating-brute-force-attacks/",
55 "https://www.elastic.co/security-labs/monitoring-okta-threats-with-elastic-security",
56 "https://www.elastic.co/security-labs/starter-guide-to-understanding-okta",
57]
58risk_score = 47
59rule_id = "94e734c0-2cda-11ef-84e1-f661ea17fbce"
60setup = "The Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule."
61severity = "medium"
62tags = [
63 "Domain: Identity",
64 "Use Case: Identity and Access Audit",
65 "Data Source: Okta",
66 "Data Source: Okta System Logs",
67 "Tactic: Credential Access",
68 "Resources: Investigation Guide",
69 "Noise: High",
70 "Performance: Normal",
71 "Profile: Aggressive",
72 "Threat: Brute Force",
73 "Rule Type: ES|QL",
74 "Platform: Okta",
75]
76timestamp_override = "event.ingested"
77type = "esql"
78
79query = '''
80FROM logs-okta.system-* METADATA _id, _version, _index
81| WHERE
82 data_stream.dataset == "okta.system"
83 AND (event.action LIKE "user.authentication.*" OR event.action == "user.session.start")
84 AND okta.outcome.reason IN ("INVALID_CREDENTIALS", "LOCKED_OUT")
85 AND okta.actor.alternate_id IS NOT NULL
86// Build user-source context as JSON for enrichment
87| EVAL Esql.user_source_info = CONCAT(
88 "{\"user\":\"", okta.actor.alternate_id,
89 "\",\"ip\":\"", COALESCE(okta.client.ip::STRING, "unknown"),
90 "\",\"user_agent\":\"", COALESCE(okta.client.user_agent.raw_user_agent, "unknown"), "\"}"
91 )
92// FIRST STATS: Aggregate by (IP, user) to get per-user attempt counts
93// This prevents skew from outlier users with many attempts
94| STATS
95 Esql.user_attempts = COUNT(*),
96 Esql.user_dt_hashes = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash),
97 Esql.user_source_info = VALUES(Esql.user_source_info),
98 Esql.user_agents_per_user = VALUES(okta.client.user_agent.raw_user_agent),
99 Esql.devices_per_user = VALUES(okta.client.device),
100 Esql.is_proxy = VALUES(okta.security_context.is_proxy),
101 Esql.geo_country = VALUES(client.geo.country_name),
102 Esql.geo_city = VALUES(client.geo.city_name),
103 Esql.asn_number = VALUES(source.as.number),
104 Esql.asn_org = VALUES(source.as.organization.name),
105 Esql.threat_suspected = VALUES(okta.debug_context.debug_data.threat_suspected),
106 Esql.risk_level = VALUES(okta.debug_context.debug_data.risk_level),
107 Esql.risk_reasons = VALUES(okta.debug_context.debug_data.risk_reasons),
108 Esql.event_actions = VALUES(event.action),
109 Esql.first_seen_user = MIN(@timestamp),
110 Esql.last_seen_user = MAX(@timestamp)
111 BY okta.client.ip, okta.actor.alternate_id
112// SECOND STATS: Aggregate by IP to detect credential stuffing pattern
113// Now we can accurately measure the distribution of attempts across users
114| STATS
115 Esql.unique_users = COUNT(*),
116 Esql.total_attempts = SUM(Esql.user_attempts),
117 Esql.max_attempts_per_user = MAX(Esql.user_attempts),
118 Esql.min_attempts_per_user = MIN(Esql.user_attempts),
119 Esql.avg_attempts_per_user = AVG(Esql.user_attempts),
120 Esql.users_with_single_attempt = SUM(CASE(Esql.user_attempts == 1, 1, 0)),
121 Esql.users_with_few_attempts = SUM(CASE(Esql.user_attempts <= 2, 1, 0)),
122 Esql.first_seen = MIN(Esql.first_seen_user),
123 Esql.last_seen = MAX(Esql.last_seen_user),
124 Esql.target_users = VALUES(okta.actor.alternate_id),
125 Esql.user_source_mapping = VALUES(Esql.user_source_info),
126 Esql.event_action_values = VALUES(Esql.event_actions),
127 Esql.user_agent_values = VALUES(Esql.user_agents_per_user),
128 Esql.device_values = VALUES(Esql.devices_per_user),
129 Esql.is_proxy_values = VALUES(Esql.is_proxy),
130 Esql.geo_country_values = VALUES(Esql.geo_country),
131 Esql.geo_city_values = VALUES(Esql.geo_city),
132 Esql.source_asn_values = VALUES(Esql.asn_number),
133 Esql.source_asn_org_values = VALUES(Esql.asn_org),
134 Esql.threat_suspected_values = VALUES(Esql.threat_suspected),
135 Esql.risk_level_values = VALUES(Esql.risk_level),
136 Esql.risk_reasons_values = VALUES(Esql.risk_reasons)
137 BY okta.client.ip
138// Calculate stuffing signature: most users should have very few attempts
139| EVAL Esql.pct_users_few_attempts = Esql.users_with_few_attempts * 100.0 / Esql.unique_users
140// Credential stuffing: many users, most with 1-2 attempts each, low max per user
141// Stacked stats gives us accurate per-user distribution instead of skewed averages
142| WHERE
143 Esql.total_attempts >= 25
144 AND Esql.unique_users >= 15
145 AND Esql.max_attempts_per_user <= 2
146 AND Esql.pct_users_few_attempts >= 80.0
147| SORT Esql.unique_users DESC
148| KEEP Esql.*, okta.client.ip
149'''
150
151
152[[rule.threat]]
153framework = "MITRE ATT&CK"
154[[rule.threat.technique]]
155id = "T1110"
156name = "Brute Force"
157reference = "https://attack.mitre.org/techniques/T1110/"
158[[rule.threat.technique.subtechnique]]
159id = "T1110.004"
160name = "Credential Stuffing"
161reference = "https://attack.mitre.org/techniques/T1110/004/"
162
163
164[rule.threat.tactic]
165id = "TA0006"
166name = "Credential Access"
167reference = "https://attack.mitre.org/tactics/TA0006/"
Triage and analysis
Investigating Potential Okta Credential Stuffing (Single Source)
This rule identifies a single source IP attempting authentication against many user accounts with minimal attempts per user. This pattern indicates credential stuffing where attackers rapidly test breached username and password pairs.
Possible investigation steps
- Identify the source IP and determine if it belongs to known proxy, VPN, or cloud infrastructure.
- Review the list of targeted user accounts and check if any authentications succeeded.
- Examine the user agent strings for signs of automation or scripting tools.
- Check if Okta flagged the source as a known threat or proxy.
- Determine if any targeted accounts have elevated privileges or access to sensitive systems.
- Review the geographic location and ASN of the source IP for anomalies.
False positive analysis
- Corporate proxies or VPN exit nodes may aggregate traffic from multiple legitimate users.
- Shared systems such as kiosks or conference room computers may have multiple users authenticating.
- Legitimate SSO integrations may generate multiple authentication attempts from a single source.
Response and remediation
- If attack is confirmed, block the source IP at the network perimeter.
- Reset passwords for any accounts that may have been compromised.
- Enable or strengthen MFA for targeted accounts.
- Review Okta sign-on policies to add additional friction for suspicious authentication patterns.
- If this is a known legitimate source, consider adding an exception for the IP or ASN.
References
Related rules
- Potential Okta Password Spray (Multi-Source)
- Potential Okta Password Spray (Single Source)
- Entra ID Sign-in Brute Force Attempted (Microsoft 365)
- Multiple Device Token Hashes for Single Okta Session
- Okta AiTM Session Cookie Replay