Techniques
Sample rules
Potential Okta Brute Force (Device Token Rotation)
- source: elastic
- technicques:
- T1110
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.
Detection logic
FROM logs-okta.system-* METADATA _id, _version, _index
| WHERE
event.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