Threat-hunting brief: Golden SAML token forgery in Azure AD / ADFS environments
Author: Thomas Malinowski Published: 2026-05-18 Status: Hunt-methodology brief, citation-disciplined Tags: APT29, Midnight Blizzard, Azure AD, Microsoft 365, Golden SAML, T1606.002, T1550.001, threat-hunting, KQL
Bottom line up front
APT29 / Midnight Blizzard demonstrated in the SolarWinds post-compromise phase (Dec 2020 – Jan 2021) that an attacker who extracts an on-prem ADFS token-signing certificate can forge SAML 2.0 assertions for any identity in the federated tenant, bypassing MFA and Conditional Access entirely. The technique is MITRE T1606.002 / T1550.001. Telemetry target: Microsoft Sentinel SigninLogs, AADServicePrincipalSignInLogs, and AuditLogs. Recommended hunt window: 90 days. Deliverable: a triage queue of sign-in events bearing the forged-token signature (no MFA claim, anomalous source IP, unconstrained audience) joined to subsequent privilege escalation in the same tenant.
Threat-model context
Cloud-first and AI-lab organisations sit in the highest-value target set. Three properties make this particularly acute:
- Federated identity is the control plane. A single ADFS signing key underpins trust for every SAML-integrated application — M365, Azure ARM, SaaS tooling, and inference infrastructure. Owning the key is equivalent to owning every account in the tenant.
- Service-principal abuse is a force multiplier. CISA AA21-008A documents APT29 using forged tokens specifically to authenticate as application service principals, which typically lack MFA and carry standing Graph API delegations.
- Multiple actors now use this playbook. Storm-0558 (2023) forged Entra ID tokens using a stolen Microsoft MSA signing key — different acquisition path, same downstream observable. See MSRC September 2023.
The hunt hypothesis
If an attacker has forged a SAML token from an on-prem ADFS signing key, then we expect:
SigninLogsentries withAuthenticationRequirement == "singleFactorAuthentication"andTokenIssuerTypeindicating federation, against a high-privilege resource (Azure ARM, Graph, Exchange Online).- The authenticating IP falls outside the organisation's known ADFS Web Application Proxy ranges — a forged token can originate from any host.
AuditLogsshowSet domain authenticationorSet federation settings on domainin the access window — attackers may embed a second signing certificate in the trust.- Within hours of the anomalous sign-in: a directory role assignment or application credential addition on the same principal.
Queries
Q1 — SAML sign-ins without MFA (T1606.002)
SigninLogs
| where TimeGenerated > ago(90d)
| where AuthenticationRequirement == "singleFactorAuthentication"
| where TokenIssuerType == "AzureAD"
| where AuthenticationDetails has_any ("SAML", "Federation")
| where ResultType == 0
| project TimeGenerated, UserPrincipalName, IPAddress,
ResourceDisplayName, ConditionalAccessStatus, Location
| order by TimeGenerated desc
Q2 — Service-principal sign-ins from anomalous country (T1550.001)
let baseline = ago(30d);
let hunt = ago(7d);
let sp_geo =
AADServicePrincipalSignInLogs
| where TimeGenerated between (ago(37d) .. baseline)
| where ResultType == 0
| summarize BaselineCountries = make_set(Location) by ServicePrincipalId;
AADServicePrincipalSignInLogs
| where TimeGenerated > hunt
| where ResultType == 0
| join kind=leftouter sp_geo on ServicePrincipalId
| where not(Location in (BaselineCountries))
| project TimeGenerated, ServicePrincipalName, IPAddress, Location, ResourceDisplayName
Q3 — Federated domain or trust modification (T1484.002)
AuditLogs
| where TimeGenerated > ago(90d)
| where OperationName in (
"Set domain authentication",
"Set federation settings on domain",
"Add unverified domain to company",
"Verify domain")
| extend InitiatedByUPN = tostring(InitiatedBy.user.userPrincipalName)
| extend TargetDomain = tostring(TargetResources[0].displayName)
| project TimeGenerated, OperationName, InitiatedByUPN, TargetDomain, Result
Q4 — Privilege escalation within 2 h of a SAML sign-in (T1606.002 → T1098.003)
let saml =
SigninLogs
| where TimeGenerated > ago(90d)
| where AuthenticationRequirement == "singleFactorAuthentication"
| where ResultType == 0
| where AuthenticationDetails has_any ("SAML", "Federation")
| project SigninTime = TimeGenerated, UserPrincipalName, IPAddress;
AuditLogs
| where TimeGenerated > ago(90d)
| where OperationName in (
"Add member to role",
"Add eligible member to role",
"Add app role assignment to service principal")
| extend TargetUPN = tostring(TargetResources[0].userPrincipalName)
| join kind=inner saml on $left.TargetUPN == $right.UserPrincipalName
| where TimeGenerated between (SigninTime .. (SigninTime + 2h))
| project TimeGenerated, OperationName, TargetUPN, IPAddress
Tuning — FP vs TP
Common FP sources:
- Service accounts on SAML with no MFA — CI/CD pipelines, monitoring agents, legacy apps. Build an allow-list and filter them from Q1.
- M&A / partner B2B onboarding — Q3 fires routinely when adding a federated partner domain. Cross-reference an open change ticket; if one exists, it's a FP.
- Cloud-NAT egress — managed workloads doing cross-cloud API calls appear as anomalous geolocation in Q2. Maintain an allow-list of cloud-provider NAT ranges.
TP indicators that raise confidence:
- Q1 and Q4 fire on the same UPN within the same hour with no change record.
- The IP in Q1 is not an ADFS proxy or WAP server address from your inventory.
- Q3 fires on an unrecognised domain or is initiated by a service principal (service principals should not be adding federated domains).
- The resource in Q1 is Azure ARM, Graph (
graph.microsoft.com), or Exchange Online.
From hypothesis to durable detection
Alert on (high-precision): Q4 hits where the SAML sign-in IP is outside known ADFS proxy ranges and no matching change record exists. Once proxy IPs are baselined, the benign rate should approach zero.
Keep as hunt (high-recall, analyst-triaged): Q1 and Q2 — both produce volume that requires context to evaluate. Run weekly; triage against service-account allow-list; escalate residual.
Integration with the existing Sigma ruleset: The Q4 pattern — anomalous SAML sign-in preceding a role assignment — extends t1098.001_azure_app_credential_addition.yml. That rule fires on credential additions to service principals. The full composite alert should chain: anomalous SAML → app credential addition → role assignment, each within a two-hour sliding window.
Pivots from a single finding
Given one confirmed suspicious SAML sign-in, fetch in this order:
- Token claims. Inspect
AuthenticationDetailsfor theamrfield. A legitimate MFA-satisfied session carries["mfa", "rsa"]or equivalent; a forged token typically carries only["pwd"]against a resource with a CA policy requiring MFA. - App-consent grants. Query
AuditLogsforAdd app role assignmentandConsent to applicationon the same principal in the 48 h following the sign-in. APT29 used this to add OAuth permissions that survive password resets (AA21-008A). - CA policy mutations.
Update conditional access policy/Delete conditional access policyinAuditLogs— an attacker with sufficient privilege weakens CA to reduce friction on subsequent access. - Mailbox access via Graph.
MailItemsAccessedin the M365 Unified Audit Log tied to the same application confirms collection impact. - ADFS on-prem events (if in scope). Windows Event ID 1202 (token issuance) on the ADFS farm for the same time window — a token accepted by Azure AD with no corresponding ADFS 1202 is a near-certain forgery.
Limitations
- Stolen refresh tokens. An attacker replaying a legitimate refresh token never touches ADFS. The sign-in carries a full MFA claim and a plausible IP. Q1–Q4 do not fire. Detection shifts to endpoint telemetry.
- Azure managed identities. Workloads using managed identities authenticate through Azure's internal IMDS endpoint — no ADFS, no
SigninLogsSAML event. This hunt is blind to that path. - ADFS trust lateral move. An attacker who adds a new ADFS server to the trust (rather than exporting the key) may not surface in Entra audit logs if the federation settings are not modified in Azure. Q3 is only effective if the attacker updates the Azure-side trust.
- Slow-and-low token use. Infrequent token use from a geolocation within the tenant's plausible range defeats Q2's baseline. Q4 remains the most durable signal because it is activity-based rather than attribute-based.
References
- CISA, FBI, NSA. AA21-008A — Detecting Post-Compromise Threat Activity in Microsoft Cloud Environments. 8 January 2021. https://www.cisa.gov/news-events/cybersecurity-advisories/aa21-008a
- Microsoft MSRC. Results of Major Technical Investigations for Storm-0558 Key Acquisition. 6 September 2023. https://web.archive.org/web/2024/https://msrc.microsoft.com/blog/2023/09/results-of-major-technical-investigations-for-storm-0558-key-acquisition/
- Microsoft Security Blog. Midnight Blizzard: Guidance for Responders on Nation-State Attack. 25 January 2024. https://www.microsoft.com/en-us/security/blog/2024/01/25/midnight-blizzard-guidance-for-responders-on-nation-state-attack/
- MITRE ATT&CK. T1606.002 — Forge Web Credentials: SAML Tokens. https://attack.mitre.org/techniques/T1606/002/ · T1550.001 — Application Access Token. https://attack.mitre.org/techniques/T1550/001/
- Sygnia. Golden SAML: Newly Discovered Attack Technique Forges Authentication to Cloud Apps. November 2017. https://www.sygnia.co/blog/golden-saml-newly-discovered-attack-technique-forges-authentication-to-cloud-apps/
- Mandiant. Remediation and Hardening Strategies for Microsoft 365 to Defend Against UNC2452. January 2021. https://cloud.google.com/blog/topics/threat-intelligence/remediation-and-hardening-strategies-for-microsoft-365-to-defend-against-unc2452/