LoFP LoFP / mobile users switching between wifi and cellular may show ip address changes. correlate with device type and typical user behavior patterns.

Techniques

Sample rules

Description

Detects potential Adversary-in-the-Middle (AiTM) session cookie replay attacks against Okta. This rule identifies when an Okta session is used from multiple IP addresses or with suspicious non-browser user agents after initial authentication. AiTM attacks capture session cookies via phishing proxies (e.g., Evilginx, Modlishka) and replay them from attacker infrastructure, bypassing MFA. The detection correlates session start events with subsequent policy evaluations or SSO attempts that occur from different IPs or programmatic user agents.

Detection logic

FROM logs-okta.system-*

// Filter to relevant event types for AiTM detection
| WHERE
    okta.event_type IN ("user.session.start", "policy.evaluate_sign_on", "user.authentication.sso") AND
    okta.authentication_context.root_session_id IS NOT NULL AND
    okta.actor.alternate_id != "system@okta.com"

// Create event type flags
| EVAL Esql.is_session_start = okta.event_type == "user.session.start"
| EVAL Esql.is_policy_eval = okta.event_type == "policy.evaluate_sign_on"
| EVAL Esql.is_sso = okta.event_type == "user.authentication.sso"
| EVAL Esql.is_replay_event = Esql.is_policy_eval OR Esql.is_sso

// Flag suspicious non-browser user agents
| EVAL Esql.is_suspicious_ua =
    user_agent.original LIKE "python-requests*" OR
    user_agent.original LIKE "curl/*" OR
    user_agent.original LIKE "httpx*" OR
    user_agent.original LIKE "aiohttp*" OR
    user_agent.original LIKE "Go-http-client*" OR
    user_agent.original LIKE "*Headless*" OR
    user_agent.original LIKE "Java/*" OR
    user_agent.original LIKE "okhttp*"

// Aggregate by session
| STATS
    Esql.session_start_count = SUM(CASE(Esql.is_session_start, 1, 0)),
    Esql.replay_event_count = SUM(CASE(Esql.is_replay_event, 1, 0)),
    Esql.session_start_time = MIN(CASE(Esql.is_session_start, @timestamp, null)),
    Esql.first_replay_time = MIN(CASE(Esql.is_replay_event, @timestamp, null)),
    Esql.last_replay_time = MAX(CASE(Esql.is_replay_event, @timestamp, null)),
    Esql.session_start_ip = MAX(CASE(Esql.is_session_start, okta.client.ip, null)),
    Esql.session_start_ua = MAX(CASE(Esql.is_session_start, user_agent.original, null)),
    Esql.suspicious_ua_count = SUM(CASE(Esql.is_suspicious_ua, 1, 0)),
    Esql.okta_client_ip_count_distinct = COUNT_DISTINCT(okta.client.ip),
    Esql.user_agent_count_distinct = COUNT_DISTINCT(user_agent.original),
    Esql.okta_client_ip_values = VALUES(okta.client.ip),
    Esql.user_agent_values = VALUES(user_agent.original),
    Esql.okta_event_type_values = VALUES(okta.event_type),
    Esql.okta_outcome_result_values = VALUES(okta.outcome.result),
    Esql.source_geo_country_name_values = VALUES(source.geo.country_name),
    Esql.source_geo_city_name_values = VALUES(source.geo.city_name),
    Esql.okta_debug_context_debug_data_risk_level_values = VALUES(okta.debug_context.debug_data.risk_level),
    Esql.okta_debug_context_debug_data_risk_reasons_values = VALUES(okta.debug_context.debug_data.risk_reasons)
  BY okta.authentication_context.root_session_id, okta.actor.alternate_id

// Detection conditions
| WHERE
    Esql.session_start_count >= 1
    AND Esql.replay_event_count >= 1
    AND Esql.first_replay_time > Esql.session_start_time
    AND (
            (
                Esql.okta_client_ip_count_distinct > 1 OR Esql.user_agent_count_distinct > 1
            ) AND Esql.suspicious_ua_count > 0
        )

| SORT Esql.session_start_time DESC
| KEEP Esql.*, okta.authentication_context.root_session_id, okta.actor.alternate_id