Okta Authentication Bypass via Skeleton Key Injection - Behavioral


Description

Detects potential Okta authentication bypass via skeleton key injection using behavioral z-score analysis.

Skeleton key attacks in Okta involve manipulating authentication policies to weaken MFA requirements (disabling requireFactor, zeroing maxSessionLifetime) and bulk-enrolling attacker-controlled authenticators on victim accounts. This detection builds a 90-day behavioral baseline for each admin's policy change and factor enrollment patterns, then identifies anomalous spikes in the last 7 days.

Detection Logic: - Z-score: Spike in security-weakening policy changes (> 2σ above baseline) - Z-score: Spike in admin-on-behalf-of MFA factor enrollments (> 3σ above baseline) - Cold-start: First-time security weakening (no prior baseline - immediate high-confidence signal) - Cold-start: First-time admin-enrolled factors for other users

Why This Matters: Skeleton key attacks require two steps: weaken authentication policies to reduce MFA friction, then enroll attacker-controlled authenticators on victim accounts. This detection catches both steps using behavioral baselines that adapt to legitimate admin workflows.

Complementary Detection: Use alongside Okta.ADAgent.TokenAbuse.Behavioral for admin credential theft scenarios.

Query · python

def rule(event):
    # Query already filtered for is_anomalous = TRUE.
    # Guard against malformed rows missing the primary key field.
    return bool(event.get("admin_email"))


def title(event):
    admin = event.get("admin_email", "Unknown")
    recent_weakenings = event.get("recent_total_weakenings") or 0
    recent_admin_enrollments = event.get("recent_total_admin_enrollments") or 0
    if recent_weakenings > 0:
        return f"Okta Skeleton Key: Security Policy Weakening by {admin}"
    if recent_admin_enrollments > 0:
        return f"Okta Skeleton Key: Bulk Admin Factor Enrollment by {admin}"
    return f"Okta Skeleton Key Bypass Anomaly Detected for {admin}"


def severity(event):
    score = event.get("anomaly_severity_score") or 0
    is_first_security_weakening = event.get("is_first_time_security_weakening") or False
    recent_weakenings = event.get("recent_total_weakenings") or 0
    if is_first_security_weakening or (recent_weakenings > 0 and score > 20):
        return "CRITICAL"
    if recent_weakenings > 0 or score > 15:
        return "HIGH"
    return "MEDIUM"


def dedup_key(event):
    admin = event.get("admin_email", "unknown")
    first_event = str(
        event.get("recent_policy_first_event")
        or event.get("recent_enrollment_first_event", "unknown")
    )[:10]
    return f"okta_skeleton_key_{admin}_{first_event}"


def alert_context(event):
    return {
        "admin_email": event.get("admin_email"),
        "recent_total_weakenings": event.get("recent_total_weakenings"),
        "recent_total_admin_enrollments": event.get("recent_total_admin_enrollments"),
        "z_score_security_weakenings": event.get("z_score_security_weakenings"),
        "z_score_admin_enrollments": event.get("z_score_admin_enrollments"),
        "anomaly_severity_score": event.get("anomaly_severity_score"),
        "is_first_time_security_weakening": event.get("is_first_time_security_weakening"),
        "is_first_time_admin_enrollment": event.get("is_first_time_admin_enrollment"),
        "baseline_total_weakenings": event.get("baseline_total_weakenings"),
        "recent_policy_first_event": event.get("recent_policy_first_event"),
        "recent_policy_last_event": event.get("recent_policy_last_event"),
    }

Analyst notes

  1. Review recent_total_weakenings and recent_max_weakenings_per_hour for admin_email against baseline_total_weakenings - query Okta SystemLog for policy.rule.update and policy.lifecycle.update events by admin_email in the 24 hours around recent_policy_first_event, focusing on changedAttributes containing requireFactor=false or maxSessionLifetimeMinutes=0
  2. Check recent_total_admin_enrollments and z_score_admin_enrollments for user.mfa.factor.activate events by admin_email in the 7 days around recent_enrollment_first_event - identify target accounts enrolled and verify whether enrollment was authorized and factor types are consistent with corporate standards
  3. Search for Okta.ADAgent.TokenAbuse.Behavioral or other privileged account alerts for admin_email in the 48 hours before recent_policy_first_event to determine whether the admin account itself was compromised prior to the policy manipulation
Raw source Okta Authentication Bypass via Skeleton Key Injection - Behavioral · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: scheduled_rule
Filename: okta_skeleton_key_bypass_behavioral.py
RuleID: "Okta.SkeletonKeyBypass.Behavioral"
DisplayName: "Okta Authentication Bypass via Skeleton Key Injection - Behavioral"
Enabled: true
ScheduledQueries:
  - Query.Okta.SkeletonKeyBypassBehavioral
Severity: High  # Default, dynamic severity in rule function
Status: Experimental
Tags:
  - Identity & Access Management
  - Okta
  - Active Directory
  - Defense Evasion:Modify Authentication Process
  - Persistence:Account Manipulation
  - Anomaly Detection
Reports:
  MITRE ATT&CK:
    - TA0005:T1556  # Modify Authentication Process
    - TA0003:T1098  # Account Manipulation
Description: |
  Detects potential Okta authentication bypass via skeleton key injection using behavioral z-score analysis.

  Skeleton key attacks in Okta involve manipulating authentication policies to weaken MFA requirements
  (disabling requireFactor, zeroing maxSessionLifetime) and bulk-enrolling attacker-controlled
  authenticators on victim accounts. This detection builds a 90-day behavioral baseline for each
  admin's policy change and factor enrollment patterns, then identifies anomalous spikes in the last
  7 days.

  **Detection Logic:**
  - Z-score: Spike in security-weakening policy changes (> 2σ above baseline)
  - Z-score: Spike in admin-on-behalf-of MFA factor enrollments (> 3σ above baseline)
  - Cold-start: First-time security weakening (no prior baseline - immediate high-confidence signal)
  - Cold-start: First-time admin-enrolled factors for other users

  **Why This Matters:**
  Skeleton key attacks require two steps: weaken authentication policies to reduce MFA friction,
  then enroll attacker-controlled authenticators on victim accounts. This detection catches both
  steps using behavioral baselines that adapt to legitimate admin workflows.

  **Complementary Detection:**
  Use alongside `Okta.ADAgent.TokenAbuse.Behavioral` for admin credential theft scenarios.

Reference: https://www.varonis.com/blog/okta-attack-vectors
Runbook: |
  1. Review recent_total_weakenings and recent_max_weakenings_per_hour for admin_email against baseline_total_weakenings - query Okta SystemLog for policy.rule.update and policy.lifecycle.update events by admin_email in the 24 hours around recent_policy_first_event, focusing on changedAttributes containing requireFactor=false or maxSessionLifetimeMinutes=0
  2. Check recent_total_admin_enrollments and z_score_admin_enrollments for user.mfa.factor.activate events by admin_email in the 7 days around recent_enrollment_first_event - identify target accounts enrolled and verify whether enrollment was authorized and factor types are consistent with corporate standards
  3. Search for Okta.ADAgent.TokenAbuse.Behavioral or other privileged account alerts for admin_email in the 48 hours before recent_policy_first_event to determine whether the admin account itself was compromised prior to the policy manipulation

DedupPeriodMinutes: 1440  # 24 hours
SummaryAttributes:
  - admin_email
  - anomaly_severity_score
  - recent_total_weakenings
Tests:
  # Positive Tests - Should Alert
  - Name: First-Time Security Policy Weakening - Critical Severity
    ExpectedResult: true
    Log:
      {
        "admin_email": "attacker@company.com",
        "baseline_total_policy_changes": 0,
        "baseline_total_weakenings": 0,
        "baseline_mean_weakenings_per_hour": 0.0,
        "baseline_total_enrollments": 0,
        "baseline_total_admin_enrollments": 0,
        "recent_total_policy_changes": 3,
        "recent_max_policy_changes_per_hour": 3,
        "recent_total_weakenings": 2,
        "recent_max_weakenings_per_hour": 2,
        "recent_total_enrollments": 0,
        "recent_total_admin_enrollments": 0,
        "z_score_policy_changes": null,
        "z_score_security_weakenings": null,
        "z_score_enrollments": null,
        "z_score_admin_enrollments": null,
        "is_policy_volume_anomaly": false,
        "is_security_weakening_anomaly": false,
        "is_admin_enrollment_anomaly": false,
        "is_first_time_security_weakening": true,
        "is_first_time_admin_enrollment": false,
        "is_anomalous": true,
        "anomaly_severity_score": 20.0,
        "recent_policy_first_event": "2024-01-15 02:00:00",
        "recent_policy_last_event": "2024-01-15 03:00:00"
      }

  - Name: Security Weakening Z-Score Anomaly with Enrollment Spike - Critical Severity
    ExpectedResult: true
    Log:
      {
        "admin_email": "compromised-admin@company.com",
        "baseline_total_policy_changes": 100,
        "baseline_active_days_policy": 30,
        "baseline_mean_policy_changes_per_hour": 2.0,
        "baseline_stddev_policy_changes_per_hour": 1.0,
        "baseline_total_weakenings": 0,
        "baseline_mean_weakenings_per_hour": 0.0,
        "recent_total_policy_changes": 20,
        "recent_max_policy_changes_per_hour": 15,
        "recent_total_weakenings": 5,
        "recent_max_weakenings_per_hour": 5,
        "baseline_total_enrollments": 50,
        "baseline_active_days_enrollment": 20,
        "baseline_mean_enrollments_per_hour": 1.0,
        "baseline_stddev_enrollments_per_hour": 0.5,
        "baseline_total_admin_enrollments": 2,
        "baseline_mean_admin_enrollments_per_hour": 0.1,
        "recent_total_enrollments": 5,
        "recent_max_enrollments_per_hour": 3,
        "recent_total_admin_enrollments": 2,
        "recent_max_admin_enrollments_per_hour": 1,
        "z_score_policy_changes": 13.0,
        "z_score_security_weakenings": 50.0,
        "z_score_enrollments": 4.0,
        "z_score_admin_enrollments": 9.0,
        "is_security_weakening_anomaly": true,
        "is_first_time_security_weakening": false,
        "is_first_time_admin_enrollment": false,
        "is_anomalous": true,
        "anomaly_severity_score": 109.0,
        "recent_policy_first_event": "2024-01-15 10:00:00",
        "recent_policy_last_event": "2024-01-15 14:00:00"
      }

  - Name: Bulk Admin Factor Enrollment Spike - High Severity
    ExpectedResult: true
    Log:
      {
        "admin_email": "svc-admin@company.com",
        "baseline_total_policy_changes": 50,
        "baseline_active_days_policy": 25,
        "baseline_total_weakenings": 0,
        "baseline_mean_weakenings_per_hour": 0.0,
        "recent_total_policy_changes": 5,
        "recent_max_policy_changes_per_hour": 2,
        "recent_total_weakenings": 0,
        "recent_max_weakenings_per_hour": 0,
        "baseline_total_enrollments": 30,
        "baseline_active_days_enrollment": 15,
        "baseline_mean_enrollments_per_hour": 0.5,
        "baseline_stddev_enrollments_per_hour": 0.3,
        "baseline_total_admin_enrollments": 1,
        "baseline_mean_admin_enrollments_per_hour": 0.02,
        "recent_total_enrollments": 20,
        "recent_max_enrollments_per_hour": 8,
        "recent_total_admin_enrollments": 8,
        "recent_max_admin_enrollments_per_hour": 8,
        "z_score_enrollments": 25.0,
        "z_score_admin_enrollments": 79.8,
        "is_policy_volume_anomaly": false,
        "is_security_weakening_anomaly": false,
        "is_admin_enrollment_anomaly": true,
        "is_first_time_security_weakening": false,
        "is_first_time_admin_enrollment": false,
        "is_anomalous": true,
        "anomaly_severity_score": 79.8,
        "recent_enrollment_first_event": "2024-01-15 09:00:00",
        "recent_enrollment_last_event": "2024-01-15 11:00:00"
      }

  - Name: First-Time Admin Enrollment - Medium Severity
    ExpectedResult: true
    Log:
      {
        "admin_email": "new-admin@company.com",
        "baseline_total_policy_changes": 0,
        "baseline_total_weakenings": 0,
        "baseline_total_enrollments": 0,
        "baseline_total_admin_enrollments": 0,
        "recent_total_policy_changes": 0,
        "recent_total_weakenings": 0,
        "recent_total_enrollments": 3,
        "recent_total_admin_enrollments": 3,
        "recent_max_admin_enrollments_per_hour": 3,
        "z_score_policy_changes": null,
        "z_score_security_weakenings": null,
        "z_score_admin_enrollments": null,
        "is_first_time_security_weakening": false,
        "is_first_time_admin_enrollment": true,
        "is_anomalous": true,
        "anomaly_severity_score": 9.0,
        "recent_enrollment_first_event": "2024-01-15 16:00:00",
        "recent_enrollment_last_event": "2024-01-15 16:00:00"
      }

  # Note: rule() only validates admin_email; anomaly thresholds are enforced at the query level.
  # All rows reaching rule() with a valid admin_email are expected to return true.

  # Negative Tests - Should Not Alert
  - Name: Malformed Row - Missing Admin Email
    ExpectedResult: false
    Log:
      {
        "baseline_total_policy_changes": 100,
        "recent_total_weakenings": 5,
        "is_anomalous": true,
        "anomaly_severity_score": 50.0
      }

  - Name: Malformed Row - Empty Admin Email String
    ExpectedResult: false
    Log:
      {
        "admin_email": "",
        "baseline_total_policy_changes": 100,
        "recent_total_weakenings": 5,
        "is_anomalous": true,
        "anomaly_severity_score": 50.0
      }


# ------ paired body: okta_skeleton_key_bypass_behavioral.py ------

def rule(event):
    # Query already filtered for is_anomalous = TRUE.
    # Guard against malformed rows missing the primary key field.
    return bool(event.get("admin_email"))


def title(event):
    admin = event.get("admin_email", "Unknown")
    recent_weakenings = event.get("recent_total_weakenings") or 0
    recent_admin_enrollments = event.get("recent_total_admin_enrollments") or 0
    if recent_weakenings > 0:
        return f"Okta Skeleton Key: Security Policy Weakening by {admin}"
    if recent_admin_enrollments > 0:
        return f"Okta Skeleton Key: Bulk Admin Factor Enrollment by {admin}"
    return f"Okta Skeleton Key Bypass Anomaly Detected for {admin}"


def severity(event):
    score = event.get("anomaly_severity_score") or 0
    is_first_security_weakening = event.get("is_first_time_security_weakening") or False
    recent_weakenings = event.get("recent_total_weakenings") or 0
    if is_first_security_weakening or (recent_weakenings > 0 and score > 20):
        return "CRITICAL"
    if recent_weakenings > 0 or score > 15:
        return "HIGH"
    return "MEDIUM"


def dedup_key(event):
    admin = event.get("admin_email", "unknown")
    first_event = str(
        event.get("recent_policy_first_event")
        or event.get("recent_enrollment_first_event", "unknown")
    )[:10]
    return f"okta_skeleton_key_{admin}_{first_event}"


def alert_context(event):
    return {
        "admin_email": event.get("admin_email"),
        "recent_total_weakenings": event.get("recent_total_weakenings"),
        "recent_total_admin_enrollments": event.get("recent_total_admin_enrollments"),
        "z_score_security_weakenings": event.get("z_score_security_weakenings"),
        "z_score_admin_enrollments": event.get("z_score_admin_enrollments"),
        "anomaly_severity_score": event.get("anomaly_severity_score"),
        "is_first_time_security_weakening": event.get("is_first_time_security_weakening"),
        "is_first_time_admin_enrollment": event.get("is_first_time_admin_enrollment"),
        "baseline_total_weakenings": event.get("baseline_total_weakenings"),
        "recent_policy_first_event": event.get("recent_policy_first_event"),
        "recent_policy_last_event": event.get("recent_policy_last_event"),
    }

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.