← Back to Explore
elasticlowTTP
Potential Okta Brute Force (Device Token Rotation)
Detects potential brute force attacks against a single Okta user account where excessive unique device token hashes are generated, indicating automated tooling that fails to persist browser cookies between attempts.
Detection Query
FROM logs-okta.system-* METADATA _id, _version, _index
| WHERE
data_stream.dataset == "okta.system"
AND (event.action LIKE "user.authentication.*" OR event.action == "user.session.start")
AND okta.outcome.reason IN ("INVALID_CREDENTIALS", "LOCKED_OUT")
AND okta.actor.alternate_id IS NOT NULL
// Primary authn endpoint; sessions API provides additional coverage
AND (
okta.debug_context.debug_data.request_uri == "/api/v1/authn"
OR okta.debug_context.debug_data.request_uri LIKE "/api/v1/sessions*"
)
// Track whether each event has a device token
| EVAL has_dt_hash = CASE(okta.debug_context.debug_data.dt_hash IS NOT NULL, 1, 0)
// Aggregate by IP + user to detect single-user brute force
| STATS
Esql.unique_dt_hashes = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash),
Esql.total_attempts = COUNT(*),
Esql.attempts_with_dt = SUM(has_dt_hash),
Esql.unique_user_agents = COUNT_DISTINCT(okta.client.user_agent.raw_user_agent),
Esql.first_seen = MIN(@timestamp),
Esql.last_seen = MAX(@timestamp),
Esql.dt_hash_values = VALUES(okta.debug_context.debug_data.dt_hash),
Esql.event_action_values = VALUES(event.action),
Esql.user_agent_values = VALUES(okta.client.user_agent.raw_user_agent),
Esql.device_values = VALUES(okta.client.device),
Esql.is_proxy_values = VALUES(okta.security_context.is_proxy),
Esql.geo_country_values = VALUES(client.geo.country_name),
Esql.geo_city_values = VALUES(client.geo.city_name),
Esql.source_asn_values = VALUES(source.as.number),
Esql.source_asn_org_values = VALUES(source.as.organization.name),
Esql.threat_suspected_values = VALUES(okta.debug_context.debug_data.threat_suspected),
Esql.risk_level_values = VALUES(okta.debug_context.debug_data.risk_level),
Esql.risk_reasons_values = VALUES(okta.debug_context.debug_data.risk_reasons)
BY okta.client.ip, okta.actor.alternate_id
// Calculate automation detection metrics (float-safe division)
| EVAL Esql.dt_coverage = Esql.attempts_with_dt * 1.0 / Esql.total_attempts,
Esql.dt_per_attempt = Esql.unique_dt_hashes * 1.0 / Esql.total_attempts
// Detection branches:
// A) Many unique DT hashes relative to attempts = tooling generating new tokens per attempt
// B) High attempts + very low DT coverage = cookie-less automation (no DT sent at all)
// C) Multiple user agents for same user = evasion or automation
| WHERE
(Esql.unique_dt_hashes >= 7 AND Esql.total_attempts >= 10 AND Esql.dt_per_attempt >= 0.5)
OR (Esql.total_attempts >= 12 AND Esql.dt_coverage < 0.15)
OR (Esql.total_attempts >= 10 AND Esql.unique_user_agents >= 5)
| SORT Esql.total_attempts DESC
| KEEP Esql.*, okta.client.ip, okta.actor.alternate_id
Author
Elastic
Created
2024/06/17
Data Sources
Okta
References
- https://support.okta.com/help/s/article/Troubleshooting-Distributed-Brute-Force-andor-Password-Spray-attacks-in-Okta
- https://www.okta.com/identity-101/brute-force/
- https://support.okta.com/help/s/article/How-does-the-Device-Token-work?language=en_US
- https://developer.okta.com/docs/reference/api/event-types/
- https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy
- https://sec.okta.com/articles/2023/08/cross-tenant-impersonation-prevention-and-detection
- https://www.okta.com/resources/whitepaper-how-adaptive-mfa-can-help-in-mitigating-brute-force-attacks/
- https://www.elastic.co/security-labs/monitoring-okta-threats-with-elastic-security
- https://www.elastic.co/security-labs/starter-guide-to-understanding-okta
Tags
Domain: IdentityUse Case: Identity and Access AuditData Source: OktaTactic: Credential AccessResources: Investigation Guide
Raw Content
[metadata]
creation_date = "2024/06/17"
integration = ["okta"]
maturity = "production"
updated_date = "2026/04/10"
[rule]
author = ["Elastic"]
description = """
Detects potential brute force attacks against a single Okta user account where excessive unique device token hashes
are generated, indicating automated tooling that fails to persist browser cookies between attempts.
"""
false_positives = [
"A user experiencing login issues may generate multiple device tokens through repeated legitimate attempts.",
"Automated testing or monitoring tools that do not persist cookies may trigger this rule.",
]
from = "now-30m"
language = "esql"
license = "Elastic License v2"
name = "Potential Okta Brute Force (Device Token Rotation)"
note = """## Triage and analysis
### Investigating Potential Okta Brute Force (Device Token Rotation)
This rule identifies excessive unique device token hashes generated for a single user account, indicating automated brute force tooling that fails to persist browser cookies between authentication attempts.
#### Possible investigation steps
- Identify the targeted user account and determine if it has elevated privileges or sensitive access.
- Review the source IP and check if it belongs to known proxy, VPN, or cloud infrastructure.
- Examine user agent strings for signs of automation, scripting tools, or inconsistent browser fingerprints.
- Check if Okta flagged the source as a known threat or proxy.
- Determine if any authentication attempts succeeded following the failed attempts.
- Review the user's recent activity for signs of account compromise.
### False positive analysis
- Users experiencing login issues may generate multiple device tokens through repeated legitimate attempts.
- Automated testing or monitoring tools that do not persist cookies may trigger this rule.
- Browser extensions or privacy tools that clear cookies between requests may cause device token rotation.
### Response and remediation
- If attack is confirmed, reset the user's password immediately.
- Block the source IP at the network perimeter.
- Review and potentially reset MFA for the targeted account.
- Monitor for any successful authentication that may indicate compromise.
- Contact the user to verify if they experienced legitimate login issues.
"""
references = [
"https://support.okta.com/help/s/article/Troubleshooting-Distributed-Brute-Force-andor-Password-Spray-attacks-in-Okta",
"https://www.okta.com/identity-101/brute-force/",
"https://support.okta.com/help/s/article/How-does-the-Device-Token-work?language=en_US",
"https://developer.okta.com/docs/reference/api/event-types/",
"https://www.elastic.co/security-labs/testing-okta-visibility-and-detection-dorothy",
"https://sec.okta.com/articles/2023/08/cross-tenant-impersonation-prevention-and-detection",
"https://www.okta.com/resources/whitepaper-how-adaptive-mfa-can-help-in-mitigating-brute-force-attacks/",
"https://www.elastic.co/security-labs/monitoring-okta-threats-with-elastic-security",
"https://www.elastic.co/security-labs/starter-guide-to-understanding-okta",
]
risk_score = 21
rule_id = "23f18264-2d6d-11ef-9413-f661ea17fbce"
setup = "The Okta Fleet integration, Filebeat module, or similarly structured data is required to be compatible with this rule."
severity = "low"
tags = [
"Domain: Identity",
"Use Case: Identity and Access Audit",
"Data Source: Okta",
"Tactic: Credential Access",
"Resources: Investigation Guide",
]
timestamp_override = "event.ingested"
type = "esql"
query = '''
FROM logs-okta.system-* METADATA _id, _version, _index
| WHERE
data_stream.dataset == "okta.system"
AND (event.action LIKE "user.authentication.*" OR event.action == "user.session.start")
AND okta.outcome.reason IN ("INVALID_CREDENTIALS", "LOCKED_OUT")
AND okta.actor.alternate_id IS NOT NULL
// Primary authn endpoint; sessions API provides additional coverage
AND (
okta.debug_context.debug_data.request_uri == "/api/v1/authn"
OR okta.debug_context.debug_data.request_uri LIKE "/api/v1/sessions*"
)
// Track whether each event has a device token
| EVAL has_dt_hash = CASE(okta.debug_context.debug_data.dt_hash IS NOT NULL, 1, 0)
// Aggregate by IP + user to detect single-user brute force
| STATS
Esql.unique_dt_hashes = COUNT_DISTINCT(okta.debug_context.debug_data.dt_hash),
Esql.total_attempts = COUNT(*),
Esql.attempts_with_dt = SUM(has_dt_hash),
Esql.unique_user_agents = COUNT_DISTINCT(okta.client.user_agent.raw_user_agent),
Esql.first_seen = MIN(@timestamp),
Esql.last_seen = MAX(@timestamp),
Esql.dt_hash_values = VALUES(okta.debug_context.debug_data.dt_hash),
Esql.event_action_values = VALUES(event.action),
Esql.user_agent_values = VALUES(okta.client.user_agent.raw_user_agent),
Esql.device_values = VALUES(okta.client.device),
Esql.is_proxy_values = VALUES(okta.security_context.is_proxy),
Esql.geo_country_values = VALUES(client.geo.country_name),
Esql.geo_city_values = VALUES(client.geo.city_name),
Esql.source_asn_values = VALUES(source.as.number),
Esql.source_asn_org_values = VALUES(source.as.organization.name),
Esql.threat_suspected_values = VALUES(okta.debug_context.debug_data.threat_suspected),
Esql.risk_level_values = VALUES(okta.debug_context.debug_data.risk_level),
Esql.risk_reasons_values = VALUES(okta.debug_context.debug_data.risk_reasons)
BY okta.client.ip, okta.actor.alternate_id
// Calculate automation detection metrics (float-safe division)
| EVAL Esql.dt_coverage = Esql.attempts_with_dt * 1.0 / Esql.total_attempts,
Esql.dt_per_attempt = Esql.unique_dt_hashes * 1.0 / Esql.total_attempts
// Detection branches:
// A) Many unique DT hashes relative to attempts = tooling generating new tokens per attempt
// B) High attempts + very low DT coverage = cookie-less automation (no DT sent at all)
// C) Multiple user agents for same user = evasion or automation
| WHERE
(Esql.unique_dt_hashes >= 7 AND Esql.total_attempts >= 10 AND Esql.dt_per_attempt >= 0.5)
OR (Esql.total_attempts >= 12 AND Esql.dt_coverage < 0.15)
OR (Esql.total_attempts >= 10 AND Esql.unique_user_agents >= 5)
| SORT Esql.total_attempts DESC
| KEEP Esql.*, okta.client.ip, okta.actor.alternate_id
'''
[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1110"
name = "Brute Force"
reference = "https://attack.mitre.org/techniques/T1110/"
[[rule.threat.technique.subtechnique]]
id = "T1110.001"
name = "Password Guessing"
reference = "https://attack.mitre.org/techniques/T1110/001/"
[rule.threat.tactic]
id = "TA0006"
name = "Credential Access"
reference = "https://attack.mitre.org/tactics/TA0006/"