LoFP LoFP / automated testing or monitoring tools that do not persist cookies may trigger this rule.

Techniques

Sample rules

Potential Okta Brute Force (Device Token Rotation)

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