Google Workspace OAuth Application Authorized with Privileged Scopes


Description

Detects when a user authorizes an OAuth application with privileged scopes in Google Workspace. Privileged scopes grant broad access to sensitive data and administrative functions.

Query · python

from panther_gsuite_helpers import gsuite_parameter_lookup

PRIVILEGED_SCOPES = [
    "admin.directory.user",
    "admin.directory.group",
    "admin.directory.domain",
    "ediscovery",
    "vault",
    "cloud_search.query",
]


def rule(event):
    scopes = event.deep_get("parameters", "scope", default=[])
    app_name = event.deep_get("id", "applicationName", default="")
    event_name = event.get("name")

    if app_name != "token" or event_name != "authorize":
        return False

    # Handle both list and string formats
    if scopes and isinstance(scopes, str):
        scopes = [scopes]

    # Check if any scope matches privileged scopes
    privileged_scopes_lower = [ps.lower() for ps in PRIVILEGED_SCOPES]

    for scope_url in scopes:
        # Extract the last part of the scope URL (e.g., "admin.directory.user" from full URL)
        scope_name = scope_url.split("/")[-1].lower()
        if scope_name in privileged_scopes_lower:
            return True

    return False


def title(event):
    actor = event.deep_get("actor", "email", default="<UNKNOWN_USER>")
    app_name = event.deep_get("parameters", "app_name", default="<UNKNOWN_APP>")

    return (
        f"Google Workspace: User [{actor}] authorized OAuth app [{app_name}] with privileged scopes"
    )


def alert_context(event):
    parameters = event.get("parameters", {})

    return {
        "actor": event.deep_get("actor", "email", default=""),
        "app_name": gsuite_parameter_lookup(parameters, "app_name"),
        "client_id": gsuite_parameter_lookup(parameters, "client_id"),
        "client_type": gsuite_parameter_lookup(parameters, "client_type"),
        "scopes": gsuite_parameter_lookup(parameters, "scope"),
        "scope_data": gsuite_parameter_lookup(parameters, "scope_data"),
        "event_type": event.get("name"),
        "ip_address": event.get("ipAddress"),
    }

Analyst notes

  1. Query GSuite.ActivityEvent logs for all OAuth token authorize events by actor:email in the 7 days before and after this alert to identify if this is part of a pattern of suspicious OAuth grants
  2. Search for the parameters:client_id across all users in the organization to determine if other users also authorized the same application
  3. Review audit logs for any actions taken using the OAuth token between the authorization timestamp and now, filtering by parameters:app_name and the authorized scopes
Raw source Google Workspace OAuth Application Authorized with Privileged Scopes · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
RuleID: "Google.Workspace.OAuth.Privileged.Scopes"
DisplayName: "Google Workspace OAuth Application Authorized with Privileged Scopes"
Filename: gsuite_oauth_privileged_scopes.py
LogTypes:
  - GSuite.ActivityEvent
Enabled: true
Severity: Info
DedupPeriodMinutes: 60
Status: Experimental
Reference: https://businessinsights.bitdefender.com/the-chain-reaction-new-methods-for-extending-local-breaches-in-google-workspace
Description: >
  Detects when a user authorizes an OAuth application with privileged scopes in Google Workspace.
  Privileged scopes grant broad access to sensitive data and administrative functions.
Runbook: |
  1. Query GSuite.ActivityEvent logs for all OAuth token authorize events by actor:email in the 7 days before and after this alert to identify if this is part of a pattern of suspicious OAuth grants
  2. Search for the parameters:client_id across all users in the organization to determine if other users also authorized the same application
  3. Review audit logs for any actions taken using the OAuth token between the authorization timestamp and now, filtering by parameters:app_name and the authorized scopes
Tags:
  - GSuite
  - Initial Access
  - Persistence
  - Account Manipulation
Reports:
  MITRE ATT&CK:
    - TA0001:T1078.004
    - TA0003:T1098
SummaryAttributes:
  - actor:email
  - p_any_ip_addresses
Tests:
  - Name: Malicious App Privileged Scopes
    ExpectedResult: true
    Log:
      actor:
        email: user@example.com
        profileId: "123456789012345678901"
      id:
        applicationName: token
        customerId: C01234abc
        time: "2024-01-15 10:30:00.000000000"
        uniqueQualifier: "987654321098765432"
      ipAddress: 192.0.2.1
      kind: admin#reports#activity
      name: authorize
      parameters:
        app_name: SuspiciousThirdPartyApp
        client_id: "123456789012-abcdefghijklmnopqrstuvwxyz123456.apps.googleusercontent.com"
        client_type: WEB
        scope:
          - https://www.googleapis.com/auth/admin.directory.user
          - https://www.googleapis.com/auth/ediscovery
          - https://www.googleapis.com/auth/drive
          - https://www.googleapis.com/auth/cloud_search.query
      type: auth

  - Name: Normal App Normal Scopes
    ExpectedResult: false
    Log:
      actor:
        email: user@example.com
        profileId: "123456789012345678901"
      id:
        applicationName: token
        customerId: C01234abc
        time: "2024-01-15 10:30:00.000000000"
        uniqueQualifier: "987654321098765432"
      ipAddress: 192.0.2.1
      kind: admin#reports#activity
      name: authorize
      parameters:
        app_name: LegitimateCalendarApp
        client_id: "123456789012-abcdefghijklmnopqrstuvwxyz123456.apps.googleusercontent.com"
        client_type: WEB
        scope:
          - https://www.googleapis.com/auth/calendar
          - https://www.googleapis.com/auth/gmail.send
      type: auth

  - Name: Non-OAuth Event
    ExpectedResult: false
    Log:
      actor:
        email: user@example.com
        profileId: "123456789012345678901"
      id:
        applicationName: admin
        customerId: C01234abc
        time: "2024-01-15 10:30:00.000000000"
        uniqueQualifier: "987654321098765432"
      ipAddress: 192.0.2.1
      kind: admin#reports#activity
      name: CREATE_USER
      type: USER_SETTINGS

  - Name: OAuth App with Single Privileged Scope
    ExpectedResult: true
    Log:
      actor:
        email: admin@example.com
        profileId: "123456789012345678901"
      id:
        applicationName: token
        customerId: C01234abc
        time: "2024-01-15 10:30:00.000000000"
        uniqueQualifier: "987654321098765432"
      ipAddress: 192.0.2.1
      kind: admin#reports#activity
      name: authorize
      parameters:
        app_name: AdminToolApp
        client_id: "123456789012-abcdefghijklmnopqrstuvwxyz123456.apps.googleusercontent.com"
        client_type: WEB
        scope:
          - https://www.googleapis.com/auth/admin.directory.user
          - https://www.googleapis.com/auth/calendar
      type: auth

# ------ paired body: gsuite_oauth_privileged_scopes.py ------

from panther_gsuite_helpers import gsuite_parameter_lookup

PRIVILEGED_SCOPES = [
    "admin.directory.user",
    "admin.directory.group",
    "admin.directory.domain",
    "ediscovery",
    "vault",
    "cloud_search.query",
]


def rule(event):
    scopes = event.deep_get("parameters", "scope", default=[])
    app_name = event.deep_get("id", "applicationName", default="")
    event_name = event.get("name")

    if app_name != "token" or event_name != "authorize":
        return False

    # Handle both list and string formats
    if scopes and isinstance(scopes, str):
        scopes = [scopes]

    # Check if any scope matches privileged scopes
    privileged_scopes_lower = [ps.lower() for ps in PRIVILEGED_SCOPES]

    for scope_url in scopes:
        # Extract the last part of the scope URL (e.g., "admin.directory.user" from full URL)
        scope_name = scope_url.split("/")[-1].lower()
        if scope_name in privileged_scopes_lower:
            return True

    return False


def title(event):
    actor = event.deep_get("actor", "email", default="<UNKNOWN_USER>")
    app_name = event.deep_get("parameters", "app_name", default="<UNKNOWN_APP>")

    return (
        f"Google Workspace: User [{actor}] authorized OAuth app [{app_name}] with privileged scopes"
    )


def alert_context(event):
    parameters = event.get("parameters", {})

    return {
        "actor": event.deep_get("actor", "email", default=""),
        "app_name": gsuite_parameter_lookup(parameters, "app_name"),
        "client_id": gsuite_parameter_lookup(parameters, "client_id"),
        "client_type": gsuite_parameter_lookup(parameters, "client_type"),
        "scopes": gsuite_parameter_lookup(parameters, "scope"),
        "scope_data": gsuite_parameter_lookup(parameters, "scope_data"),
        "event_type": event.get("name"),
        "ip_address": event.get("ipAddress"),
    }

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.