Azure Service Principal Credentials Added


Description

Detects when new credentials (client secrets or certificates) are added to Microsoft Entra ID service principals or applications. Service principals are identities used by applications, services, and automation tools to access Azure resources, and they authenticate using credentials such as client secrets or certificates. Adversaries who compromise administrative credentials may add rogue credentials to existing service principals to establish persistent access that bypasses multi-factor authentication (MFA) requirements, as service principal authentication uses client credentials rather than interactive user login.

Query · python

CREDENTIAL_OPERATION = "add service principal credentials"


def rule(event):
    # Check for service principal credential addition operations
    operation_name = event.get("operationName", "").lower()
    activity_display_name = event.deep_get("properties", "activityDisplayName", default="").lower()

    return CREDENTIAL_OPERATION in operation_name or CREDENTIAL_OPERATION in activity_display_name


def title(event):
    actor = event.deep_get(
        "properties", "initiatedBy", "user", "userPrincipalName", default="<UNKNOWN_ACTOR>"
    )

    # Get service principal name from target resources
    service_principal = "<UNKNOWN_SP>"
    target_resources = event.deep_get("properties", "targetResources", default=[])
    for resource in target_resources or []:
        if resource.get("type") in ["ServicePrincipal", "Application"]:
            service_principal = resource.get("displayName", service_principal)
            break

    return (
        f"Service Principal Credentials Added: [{actor}] added credentials "
        f"to service principal [{service_principal}]"
    )


def alert_context(event):
    # Build context for audit logs (not sign-in logs)
    context = {
        "tenantId": event.get("tenantId", "<NO_TENANTID>"),
        "operation_name": event.get("operationName", "<NO_OPERATION>"),
        "activity_display_name": event.deep_get(
            "properties", "activityDisplayName", default="<NO_ACTIVITY>"
        ),
        "category": event.get("category", "<NO_CATEGORY>"),
        "result": event.deep_get("properties", "result", default="<NO_RESULT>"),
        "actor_user": event.deep_get(
            "properties", "initiatedBy", "user", "userPrincipalName", default="<NO_ACTOR>"
        ),
        "initiator_user_id": event.deep_get(
            "properties", "initiatedBy", "user", "id", default="<NO_USER_ID>"
        ),
        "initiator_display_name": event.deep_get(
            "properties", "initiatedBy", "user", "displayName", default="<NO_DISPLAY_NAME>"
        ),
        "source_ip": event.deep_get(
            "properties", "initiatedBy", "user", "ipAddress", default="<NO_IP>"
        ),
    }

    # Add target service principal details
    target_resources = event.deep_get("properties", "targetResources", default=[])
    service_principals = []

    for resource in target_resources or []:
        if resource.get("type") in ["ServicePrincipal", "Application"]:
            sp_info = {
                "id": resource.get("id", ""),
                "displayName": resource.get("displayName", ""),
                "type": resource.get("type"),
            }

            # Extract credential details from modified properties
            credential_info = [
                {"property": prop.get("displayName"), "new_value": prop.get("newValue", "")}
                for prop in resource.get("modifiedProperties", [])
                if prop.get("displayName") in ["KeyDescription", "KeyType", "KeyUsage"]
            ]

            if credential_info:
                sp_info["credential_details"] = credential_info

            service_principals.append(sp_info)

    if service_principals:
        context["target_service_principals"] = service_principals

    return context

Analyst notes

  1. Query Azure.Audit logs for all operations performed by properties:initiatedBy:user:userPrincipalName in the 7 days before and after this credential addition to determine if this was part of a broader compromise campaign involving multiple service principal modifications or privilege escalations
  2. Verify with the administrator whether this credential addition was authorized through your organization's change management process and review the credential type (client secret vs certificate), expiration date, and whether it aligns with standard security practices for the affected service principal
  3. Query Azure sign-in logs and API access logs for authentication activity using the affected service principal (properties:targetResources:id) in the 24 hours after credential addition to identify any suspicious resource access, data exfiltration, or privilege abuse that may indicate the credential is being misused by an attacker
Raw source Azure Service Principal Credentials Added · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
Filename: azure_service_principal_credentials_added.py
RuleID: "Azure.Audit.ServicePrincipalCredentialsAdded"
DisplayName: "Azure Service Principal Credentials Added"
Enabled: true
LogTypes:
  - Azure.Audit
Severity: Medium
Description: >
  Detects when new credentials (client secrets or certificates) are added to Microsoft Entra ID
  service principals or applications. Service principals are identities used by applications, services,
  and automation tools to access Azure resources, and they authenticate using credentials such as client
  secrets or certificates. Adversaries who compromise administrative credentials may add rogue credentials
  to existing service principals to establish persistent access that bypasses multi-factor authentication
  (MFA) requirements, as service principal authentication uses client credentials rather than interactive
  user login.
Reports:
  MITRE ATT&CK:
    - TA0003:T1098
    - TA0003:T1098.001
Runbook: |
  1. Query Azure.Audit logs for all operations performed by properties:initiatedBy:user:userPrincipalName in the 7 days before and after this credential addition to determine if this was part of a broader compromise campaign involving multiple service principal modifications or privilege escalations
  2. Verify with the administrator whether this credential addition was authorized through your organization's change management process and review the credential type (client secret vs certificate), expiration date, and whether it aligns with standard security practices for the affected service principal
  3. Query Azure sign-in logs and API access logs for authentication activity using the affected service principal (properties:targetResources:id) in the 24 hours after credential addition to identify any suspicious resource access, data exfiltration, or privilege abuse that may indicate the credential is being misused by an attacker
Reference: https://github.com/elastic/detection-rules/blob/main/rules/integrations/azure/persistence_entra_id_service_principal_credentials_added.toml
SummaryAttributes:
  - properties:initiatedBy:user:userPrincipalName
  - properties:targetResources:displayName
  - properties:targetResources:type
Tests:
  - Name: Add Service Principal Credentials
    ExpectedResult: true
    Log:
      {
        "time": "2025-01-15 09:30:20.123",
        "resourceId": "/tenants/tenant-123/providers/Microsoft.aadiam",
        "operationName": "Add service principal credentials",
        "operationVersion": "1.0",
        "category": "ApplicationManagement",
        "tenantId": "tenant-123",
        "resultSignature": "None",
        "durationMs": 0,
        "callerIpAddress": "2.2.2.2",
        "correlationId": "sp-creds-001",
        "Level": "4",
        "properties":
          {
            "result": "success",
            "operationName": "Add service principal credentials",
            "activityDisplayName": "Add service principal credentials",
            "activityDateTime": "2025-01-15T09:30:20.1234567Z",
            "loggedByService": "Core Directory",
            "operationType": "Update",
            "initiatedBy":
              {
                "user":
                  {
                    "id": "admin-123",
                    "displayName": "IT Administrator",
                    "userPrincipalName": "frodo@lotr.com",
                    "ipAddress": "2.2.2.2",
                  },
              },
            "targetResources":
              [
                {
                  "id": "sp-app-456",
                  "displayName": "Production API Service",
                  "type": "ServicePrincipal",
                  "modifiedProperties":
                    [
                      {
                        "displayName": "KeyDescription",
                        "oldValue": null,
                        "newValue": "\"Client Secret for API Access\"",
                      },
                      {
                        "displayName": "KeyType",
                        "oldValue": null,
                        "newValue": "\"Password\"",
                      },
                    ],
                },
              ],
          },
        "p_event_time": "2025-01-15 09:30:20.123",
        "p_log_type": "Azure.Audit",
      }
  - Name: Create Service Principal
    ExpectedResult: false
    Log:
      {
        "time": "2025-01-15 15:05:25.678",
        "resourceId": "/tenants/tenant-ghi/providers/Microsoft.aadiam",
        "operationName": "Add service principal",
        "operationVersion": "1.0",
        "category": "ApplicationManagement",
        "tenantId": "tenant-ghi",
        "resultSignature": "None",
        "durationMs": 0,
        "callerIpAddress": "192.0.2.250",
        "correlationId": "sp-create-001",
        "Level": "4",
        "properties":
          {
            "result": "success",
            "operationName": "Add service principal",
            "activityDisplayName": "Add service principal",
            "activityDateTime": "2025-01-15T15:05:25.6789012Z",
            "loggedByService": "Core Directory",
            "operationType": "Add",
            "initiatedBy":
              {
                "user":
                  {
                    "id": "admin-cloud-456",
                    "displayName": "Cloud Administrator",
                    "userPrincipalName": "cloudadmin@company.com",
                    "ipAddress": "192.0.2.250",
                  },
              },
            "targetResources":
              [
                {
                  "id": "sp-new-789",
                  "displayName": "New Automation Service",
                  "type": "ServicePrincipal",
                },
              ],
          },
        "p_event_time": "2025-01-15 15:05:25.678",
        "p_log_type": "Azure.Audit",
      }

# ------ paired body: azure_service_principal_credentials_added.py ------

CREDENTIAL_OPERATION = "add service principal credentials"


def rule(event):
    # Check for service principal credential addition operations
    operation_name = event.get("operationName", "").lower()
    activity_display_name = event.deep_get("properties", "activityDisplayName", default="").lower()

    return CREDENTIAL_OPERATION in operation_name or CREDENTIAL_OPERATION in activity_display_name


def title(event):
    actor = event.deep_get(
        "properties", "initiatedBy", "user", "userPrincipalName", default="<UNKNOWN_ACTOR>"
    )

    # Get service principal name from target resources
    service_principal = "<UNKNOWN_SP>"
    target_resources = event.deep_get("properties", "targetResources", default=[])
    for resource in target_resources or []:
        if resource.get("type") in ["ServicePrincipal", "Application"]:
            service_principal = resource.get("displayName", service_principal)
            break

    return (
        f"Service Principal Credentials Added: [{actor}] added credentials "
        f"to service principal [{service_principal}]"
    )


def alert_context(event):
    # Build context for audit logs (not sign-in logs)
    context = {
        "tenantId": event.get("tenantId", "<NO_TENANTID>"),
        "operation_name": event.get("operationName", "<NO_OPERATION>"),
        "activity_display_name": event.deep_get(
            "properties", "activityDisplayName", default="<NO_ACTIVITY>"
        ),
        "category": event.get("category", "<NO_CATEGORY>"),
        "result": event.deep_get("properties", "result", default="<NO_RESULT>"),
        "actor_user": event.deep_get(
            "properties", "initiatedBy", "user", "userPrincipalName", default="<NO_ACTOR>"
        ),
        "initiator_user_id": event.deep_get(
            "properties", "initiatedBy", "user", "id", default="<NO_USER_ID>"
        ),
        "initiator_display_name": event.deep_get(
            "properties", "initiatedBy", "user", "displayName", default="<NO_DISPLAY_NAME>"
        ),
        "source_ip": event.deep_get(
            "properties", "initiatedBy", "user", "ipAddress", default="<NO_IP>"
        ),
    }

    # Add target service principal details
    target_resources = event.deep_get("properties", "targetResources", default=[])
    service_principals = []

    for resource in target_resources or []:
        if resource.get("type") in ["ServicePrincipal", "Application"]:
            sp_info = {
                "id": resource.get("id", ""),
                "displayName": resource.get("displayName", ""),
                "type": resource.get("type"),
            }

            # Extract credential details from modified properties
            credential_info = [
                {"property": prop.get("displayName"), "new_value": prop.get("newValue", "")}
                for prop in resource.get("modifiedProperties", [])
                if prop.get("displayName") in ["KeyDescription", "KeyType", "KeyUsage"]
            ]

            if credential_info:
                sp_info["credential_details"] = credential_info

            service_principals.append(sp_info)

    if service_principals:
        context["target_service_principals"] = service_principals

    return context

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.