Salesforce OAuth Credential Abuse Detection


Description

Detects OAuth credential abuse and suspicious token usage patterns in Salesforce. OAuth tokens provide API access and can be abused if compromised, making this detection critical for: - Stolen or leaked OAuth tokens - Token replay attacks - Excessive API usage indicating automated abuse - Failed token refresh attempts (potential brute force) - Unauthorized token revocations

This detection triggers on OAuth-related security events and adjusts severity based on: - Token revocation events (may indicate compromise response) - Failed OAuth operations (potential attack attempts) - Excessive API usage patterns

Query · python

def rule(event):
    # Alert on OAuth-related events that may indicate credential abuse
    event_type = event.get("EVENT_TYPE", "")

    # Monitor OAuth token usage and authentication events
    oauth_events = [
        "OAuthTokenRevoked",
        "OAuthTokenRefreshFailed",
        "ApiTotalUsage",
        "ApiConnectedApp",
    ]

    return event_type in oauth_events or "oauth" in str(event_type).lower()


def title(event):
    # Create descriptive title based on event type
    event_type = event.get("EVENT_TYPE", "<UNKNOWN_EVENT>")
    user = event.get("USER_NAME", event.get("USER_ID", "<UNKNOWN_USER>"))
    app_name = event.get("CONNECTED_APP_NAME", event.get("CLIENT_NAME", "<UNKNOWN_APP>"))

    # Special handling for different event types
    if "Revoked" in event_type:
        return f"Salesforce OAuth Token Revoked: {app_name} - User: {user}"
    if "Failed" in event_type:
        return f"Salesforce OAuth Token Refresh Failed: {app_name} - User: {user}"

    return f"Salesforce OAuth Activity: {event_type} - {app_name} - User: {user}"


def severity(event):
    # Map based on event type and context
    event_type = event.get("EVENT_TYPE", "")
    status = str(event.get("STATUS", "")).lower()

    # Token revocation may indicate compromise
    if "Revoked" in event_type:
        return "HIGH"

    # Failed token operations are suspicious
    if "Failed" in event_type or "fail" in status:
        return "MEDIUM"

    # Excessive API usage may indicate abuse
    api_calls = event.get("API_TOTAL_COUNT", 0)
    # Ensure api_calls is numeric
    api_calls = api_calls if isinstance(api_calls, (int, float)) else 0
    if api_calls > 10000:
        return "HIGH"
    if api_calls > 5000:
        return "MEDIUM"

    return "DEFAULT"


def dedup(event):
    # Deduplicate by event type, user, and app
    event_type = event.get("EVENT_TYPE", "unknown")
    user_id = event.get("USER_ID", "unknown")
    app_id = event.get("CONNECTED_APP_ID", event.get("CLIENT_ID", "unknown"))
    return f"SF_OAUTH_ABUSE_{event_type}_{user_id}_{app_id}"


def alert_context(event):
    # Provide comprehensive context for investigation
    return {
        "Event Type": event.get("EVENT_TYPE"),
        "User ID": event.get("USER_ID"),
        "Username": event.get("USER_NAME"),
        "Connected App ID": event.get("CONNECTED_APP_ID"),
        "Connected App Name": event.get("CONNECTED_APP_NAME"),
        "Client ID": event.get("CLIENT_ID"),
        "Client Name": event.get("CLIENT_NAME"),
        "Source IP": event.get("SOURCE_IP"),
        "Status": event.get("STATUS"),
        "API Total Count": event.get("API_TOTAL_COUNT"),
        "Request ID": event.get("REQUEST_ID"),
        "Organization ID": event.get("ORGANIZATION_ID"),
        "Session Key": event.get("SESSION_KEY"),
    }

Analyst notes

  1. Identify the event type and severity of the OAuth activity
  2. Determine if this is a revocation (possible compromise) or failure (attack attempt)
  3. Review the user and connected app involved
  4. Check for multiple failed attempts from the same source
  5. Investigate the source IP and geolocation
  6. Review API usage patterns for anomalies
  7. If token compromise suspected: - Immediately revoke all active OAuth tokens for the affected app/user - Reset user credentials - Review all API calls made using the compromised token - Check for data exfiltration or unauthorized changes - Contact the connected app owner if third-party
  8. Consider implementing OAuth token rotation policies and IP restrictions
Raw source Salesforce OAuth Credential Abuse Detection · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
Description: |
  Detects OAuth credential abuse and suspicious token usage patterns in Salesforce. OAuth tokens provide API access and can be abused if compromised, making this detection critical for:
  - Stolen or leaked OAuth tokens
  - Token replay attacks
  - Excessive API usage indicating automated abuse
  - Failed token refresh attempts (potential brute force)
  - Unauthorized token revocations

  This detection triggers on OAuth-related security events and adjusts severity based on:
  - Token revocation events (may indicate compromise response)
  - Failed OAuth operations (potential attack attempts)
  - Excessive API usage patterns
DisplayName: "Salesforce OAuth Credential Abuse Detection"
Enabled: true
Filename: salesforce_oauth_credential_abuse.py
Runbook: |
  1. Identify the event type and severity of the OAuth activity
  2. Determine if this is a revocation (possible compromise) or failure (attack attempt)
  3. Review the user and connected app involved
  4. Check for multiple failed attempts from the same source
  5. Investigate the source IP and geolocation
  6. Review API usage patterns for anomalies
  7. If token compromise suspected:
     - Immediately revoke all active OAuth tokens for the affected app/user
     - Reset user credentials
     - Review all API calls made using the compromised token
     - Check for data exfiltration or unauthorized changes
     - Contact the connected app owner if third-party
  8. Consider implementing OAuth token rotation policies and IP restrictions
Reference: https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_oauthtoken.htm
Severity: Medium
Tests:
  - ExpectedResult: true
    Log:
      EVENT_TYPE: OAuthTokenRevoked
      TIMESTAMP: "2024-01-30 17:45:30.123"
      TIMESTAMP_DERIVED: "2024-01-30 17:45:30.123"
      EVENT_DATE: "2024-01-30"
      ORGANIZATION_ID: 00D5f000005uVo7
      USER_ID: 0055f00000RzTCE
      USER_ID_DERIVED: 0055f00000RzTCEIII
      USER_NAME: security.admin@company.com
      USER_TYPE: Standard
      SOURCE_IP: 10.10.10.10
      REQUEST_ID: 9HzSED8SpDD3JDW-CCZkX-
      CONNECTED_APP_ID: 0H05f000000XzDqEEN
      CONNECTED_APP_NAME: CompromisedApp
      CLIENT_ID: 3MVG9yZ.WNe6byQCPj8xYzKlMqR
      STATUS: Success
      SESSION_KEY: kN3pP6huf17t3/Ao
      p_event_time: "2024-01-30 17:45:30.123"
      p_parse_time: "2024-01-30 17:46:15.456"
      p_log_type: Salesforce.OAuthTokenRevoked
      p_row_id: o2t7r89s1pq3t5s4o7r0t2p6s9r1o3p5
      p_source_id: f7e3c18d-837b-461f-9c2e-7f2g4ffa2c17
      p_source_label: Salesforce - Production
      p_any_ip_addresses:
        - 10.10.10.10
      p_any_usernames:
        - security.admin@company.com
      p_any_actor_ids:
        - 0055f00000RzTCE
      p_any_trace_ids:
        - 9HzSED8SpDD3JDW-CCZkX-
    Name: OAuth Token Revoked - High Severity
  - ExpectedResult: true
    Log:
      EVENT_TYPE: OAuthTokenRefreshFailed
      TIMESTAMP: "2024-01-31 11:20:15.789"
      TIMESTAMP_DERIVED: "2024-01-31 11:20:15.789"
      EVENT_DATE: "2024-01-31"
      ORGANIZATION_ID: 00D5f000005uVo7
      USER_ID: 0055f00000SzUDF
      USER_ID_DERIVED: 0055f00000SzUDFJJJ
      USER_NAME: api.user@company.com
      USER_TYPE: Standard
      SOURCE_IP: 203.45.67.89
      REQUEST_ID: 0IATFe9TqEE4KEY-DDAlY-
      CONNECTED_APP_ID: 0H05f000000XzErFFO
      CONNECTED_APP_NAME: SuspiciousIntegration
      CLIENT_ID: 3MVG9yZ.WNe6byQCPj8xYzKl123
      STATUS: Failed
      SESSION_KEY: lO4qQ7ivg28u4/Bp
      p_event_time: "2024-01-31 11:20:15.789"
      p_parse_time: "2024-01-31 11:21:03.123"
      p_log_type: Salesforce.OAuthTokenRefreshFailed
      p_row_id: p3u8s90t2qr4u6t5p8s1u3q7t0s2p4q6
      p_source_id: f7e3c18d-837b-461f-9c2e-7f2g4ffa2c17
      p_source_label: Salesforce - Production
      p_any_ip_addresses:
        - 203.45.67.89
      p_any_usernames:
        - api.user@company.com
      p_any_actor_ids:
        - 0055f00000SzUDF
      p_any_trace_ids:
        - 0IATFe9TqEE4KEY-DDAlY-
    Name: OAuth Token Refresh Failed - Medium Severity
  - ExpectedResult: true
    Log:
      EVENT_TYPE: ApiTotalUsage
      TIMESTAMP: "2024-02-01 14:30:45.456"
      TIMESTAMP_DERIVED: "2024-02-01 14:30:45.456"
      EVENT_DATE: "2024-02-01"
      ORGANIZATION_ID: 00D5f000005uVo7
      USER_ID: 0055f00000TzVEG
      USER_ID_DERIVED: 0055f00000TzVEGKKK
      USER_NAME: integration.service@company.com
      USER_TYPE: Standard
      SOURCE_IP: 192.168.1.100
      REQUEST_ID: 1JBUGf0UrFF5LFZ-EEBmZ-
      CONNECTED_APP_ID: 0H05f000000XzFsGGP
      CONNECTED_APP_NAME: HighVolumeApp
      CLIENT_ID: 3MVG9yZ.WNe6byQCPj8xYzKl456
      API_TOTAL_COUNT: 15000
      STATUS: Success
      SESSION_KEY: mP5rR8jwh39v5/Cq
      p_event_time: "2024-02-01 14:30:45.456"
      p_parse_time: "2024-02-01 14:31:28.789"
      p_log_type: Salesforce.ApiTotalUsage
      p_row_id: q4v9t01u3rs5v7u6q9t2v4r8u1t3q5r7
      p_source_id: f7e3c18d-837b-461f-9c2e-7f2g4ffa2c17
      p_source_label: Salesforce - Production
      p_any_ip_addresses:
        - 192.168.1.100
      p_any_usernames:
        - integration.service@company.com
      p_any_actor_ids:
        - 0055f00000TzVEG
      p_any_trace_ids:
        - 1JBUGf0UrFF5LFZ-EEBmZ-
    Name: Excessive API Usage - High Severity
  - ExpectedResult: true
    Log:
      EVENT_TYPE: ApiConnectedApp
      TIMESTAMP: "2024-02-02 09:15:20.123"
      TIMESTAMP_DERIVED: "2024-02-02 09:15:20.123"
      EVENT_DATE: "2024-02-02"
      ORGANIZATION_ID: 00D5f000005uVo7
      USER_ID: 0055f00000UzWFH
      USER_ID_DERIVED: 0055f00000UzWFHLLL
      USER_NAME: normal.integration@company.com
      USER_TYPE: Standard
      SOURCE_IP: 172.16.0.100
      REQUEST_ID: 2KCVHg1VsGG6MGZ-FFCna-
      CONNECTED_APP_ID: 0H05f000000XzGtHHQ
      CONNECTED_APP_NAME: NormalApp
      CLIENT_ID: 3MVG9yZ.WNe6byQCPj8xYzKl789
      API_TOTAL_COUNT: 500
      STATUS: Success
      SESSION_KEY: nQ6sS9kxi40w6/Dr
      p_event_time: "2024-02-02 09:15:20.123"
      p_parse_time: "2024-02-02 09:16:05.456"
      p_log_type: Salesforce.ApiConnectedApp
      p_row_id: r5w0u12v4st6w8v7r0u3w5s9v2u4r6s8
      p_source_id: f7e3c18d-837b-461f-9c2e-7f2g4ffa2c17
      p_source_label: Salesforce - Production
      p_any_ip_addresses:
        - 172.16.0.100
      p_any_usernames:
        - normal.integration@company.com
      p_any_actor_ids:
        - 0055f00000UzWFH
      p_any_trace_ids:
        - 2KCVHg1VsGG6MGZ-FFCna-
    Name: Normal OAuth Usage - Default Severity
  - ExpectedResult: false
    Log:
      EVENT_TYPE: Login
      TIMESTAMP: "2024-02-03 10:30:15.789"
      TIMESTAMP_DERIVED: "2024-02-03 10:30:15.789"
      ORGANIZATION_ID: 00D5f000005uVo7
      USER_ID: 0055f00000VzXGI
      USER_ID_DERIVED: 0055f00000VzXGIMMM
      USER_NAME: regular.user@company.com
      USER_TYPE: Standard
      LOGIN_STATUS: LOGIN_NO_ERROR
      SOURCE_IP: 10.10.10.10
      REQUEST_ID: 3LDWIh2WtHH7NHa-GGDob-
      API_VERSION: "59.0"
      BROWSER_TYPE: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
      p_event_time: "2024-02-03 10:30:15.789"
      p_parse_time: "2024-02-03 10:31:02.123"
      p_log_type: Salesforce.Login
      p_row_id: s6x1v23w5tu7x9w8s1v4x6t0w3v5s7t9
      p_source_id: f7e3c18d-837b-461f-9c2e-7f2g4ffa2c17
      p_source_label: Salesforce - Production
      p_any_ip_addresses:
        - 10.10.10.10
      p_any_usernames:
        - regular.user@company.com
      p_any_actor_ids:
        - 0055f00000VzXGI
      p_any_trace_ids:
        - 3LDWIh2WtHH7NHa-GGDob-
    Name: Normal Login Event - Not OAuth
DedupPeriodMinutes: 60
LogTypes:
  - Salesforce.RealtimeEvent
RuleID: "Salesforce.OAuth.Credential.Abuse"
Threshold: 1
Tags:
  - Salesforce
  - OAuth
  - Credential Abuse
  - Token Theft
  - API Abuse
Reports:
  MITRE ATT&CK:
    - TA0006:T1528  # Credential Access: Steal Application Access Token
    - TA0006:T1110  # Credential Access: Brute Force
    - TA0005:T1550  # Defense Evasion: Use Alternate Authentication Material
    - TA0010:T1020  # Exfiltration: Automated Exfiltration


# ------ paired body: salesforce_oauth_credential_abuse.py ------

def rule(event):
    # Alert on OAuth-related events that may indicate credential abuse
    event_type = event.get("EVENT_TYPE", "")

    # Monitor OAuth token usage and authentication events
    oauth_events = [
        "OAuthTokenRevoked",
        "OAuthTokenRefreshFailed",
        "ApiTotalUsage",
        "ApiConnectedApp",
    ]

    return event_type in oauth_events or "oauth" in str(event_type).lower()


def title(event):
    # Create descriptive title based on event type
    event_type = event.get("EVENT_TYPE", "<UNKNOWN_EVENT>")
    user = event.get("USER_NAME", event.get("USER_ID", "<UNKNOWN_USER>"))
    app_name = event.get("CONNECTED_APP_NAME", event.get("CLIENT_NAME", "<UNKNOWN_APP>"))

    # Special handling for different event types
    if "Revoked" in event_type:
        return f"Salesforce OAuth Token Revoked: {app_name} - User: {user}"
    if "Failed" in event_type:
        return f"Salesforce OAuth Token Refresh Failed: {app_name} - User: {user}"

    return f"Salesforce OAuth Activity: {event_type} - {app_name} - User: {user}"


def severity(event):
    # Map based on event type and context
    event_type = event.get("EVENT_TYPE", "")
    status = str(event.get("STATUS", "")).lower()

    # Token revocation may indicate compromise
    if "Revoked" in event_type:
        return "HIGH"

    # Failed token operations are suspicious
    if "Failed" in event_type or "fail" in status:
        return "MEDIUM"

    # Excessive API usage may indicate abuse
    api_calls = event.get("API_TOTAL_COUNT", 0)
    # Ensure api_calls is numeric
    api_calls = api_calls if isinstance(api_calls, (int, float)) else 0
    if api_calls > 10000:
        return "HIGH"
    if api_calls > 5000:
        return "MEDIUM"

    return "DEFAULT"


def dedup(event):
    # Deduplicate by event type, user, and app
    event_type = event.get("EVENT_TYPE", "unknown")
    user_id = event.get("USER_ID", "unknown")
    app_id = event.get("CONNECTED_APP_ID", event.get("CLIENT_ID", "unknown"))
    return f"SF_OAUTH_ABUSE_{event_type}_{user_id}_{app_id}"


def alert_context(event):
    # Provide comprehensive context for investigation
    return {
        "Event Type": event.get("EVENT_TYPE"),
        "User ID": event.get("USER_ID"),
        "Username": event.get("USER_NAME"),
        "Connected App ID": event.get("CONNECTED_APP_ID"),
        "Connected App Name": event.get("CONNECTED_APP_NAME"),
        "Client ID": event.get("CLIENT_ID"),
        "Client Name": event.get("CLIENT_NAME"),
        "Source IP": event.get("SOURCE_IP"),
        "Status": event.get("STATUS"),
        "API Total Count": event.get("API_TOTAL_COUNT"),
        "Request ID": event.get("REQUEST_ID"),
        "Organization ID": event.get("ORGANIZATION_ID"),
        "Session Key": event.get("SESSION_KEY"),
    }

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.