Service accounts executing remote PowerShell commands may indicate unauthorized lateral movement or persistence by adversaries. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential compromise of privileged accounts and mitigate advanced threat activity.
KQL Query
let InteractiveTypes = pack_array( // Declare Interactive logon type names
'Interactive',
'CachedInteractive',
'Unlock',
'RemoteInteractive',
'CachedRemoteInteractive',
'CachedUnlock'
);
let WhitelistedCmdlets = pack_array( // List of whitelisted commands that don't provide a lot of value
'prompt',
'Out-Default',
'out-lineoutput',
'format-default',
'Set-StrictMode',
'TabExpansion2'
);
let WhitelistedAccounts = pack_array('FakeWhitelistedAccount'); // List of accounts that are known to perform this activity in the environment and can be ignored
DeviceLogonEvents // Get all logon events...
| where AccountName !in~ (WhitelistedAccounts) // ...where it is not a whitelisted account...
| where ActionType == "LogonSuccess" // ...and the logon was successful...
| where AccountName !contains "$" // ...and not a machine logon.
| where AccountName !has "winrm va_" // WinRM will have pseudo account names that match this if there is an explicit permission for an admin to run the cmdlet, so assume it is good.
| extend IsInteractive=(LogonType in (InteractiveTypes)) // Determine if the logon is interactive (True=1,False=0)...
| summarize HasInteractiveLogon=max(IsInteractive) // ...then bucket and get the maximum interactive value (0 or 1)...
by AccountName // ... by the AccountNames
| where HasInteractiveLogon == 0 // ...and filter out all accounts that had an interactive logon.
// At this point, we have a list of accounts that we believe to be service accounts
// Now we need to find RemotePS sessions that were spawned by those accounts
// Note that we look at all powershell cmdlets executed to form a 29-day baseline to evaluate the data on today
| join kind=rightsemi ( // Start by dropping the account name and only tracking the...
DeviceEvents // ...
| where ActionType == 'PowerShellCommand' // ...PowerShell commands seen...
| where InitiatingProcessFileName =~ 'wsmprovhost.exe' // ...whose parent was wsmprovhost.exe (RemotePS Server)...
| extend AccountName = InitiatingProcessAccountName // ...and add an AccountName field so the join is easier
) on AccountName
// At this point, we have all of the commands that were ran by service accounts
| extend Command = tostring(extractjson('$.Command', AdditionalFields)) // Extract the actual PowerShell command that was executed
| where Command !in (WhitelistedCmdlets) // Remove any values that match the whitelisted cmdlets
| summarize (Timestamp, ReportId)=argmax(Timestamp, ReportId), // Then group all of the cmdlets and calculate the min/max times of execution...
makeset(Command), count(), min(Timestamp) by // ...as well as creating a list of cmdlets ran and the count..
AccountName, DeviceName, DeviceId // ...and have the commonality be the account, DeviceName and DeviceId
// At this point, we have machine-account pairs along with the list of commands run as well as the first/last time the commands were ran
| order by AccountName asc // Order the final list by AccountName just to make it easier to go through
| where min_Timestamp > ago(1d) // Included to restrict the scope for the custom detection page
id: 9fb83ddb-ff6c-49e4-920a-ac72e63c191d
name: ServiceAccountsPerformingRemotePS
description: |
Service Accounts Performing Remote PowerShell.
Author: miflower.
The purpose behind this detection is for finding service accounts that are performing remote powershell sessions.
There are two phases to the detection: Identify service accounts, Find remote PS cmdlets being ran by these accounts.
To accomplish this, we utilize DeviceLogonEvents and DeviceEvents to find cmdlets ran that meet the criteria.
One of the main advantages of this method is that only requires server telemetry, and not the attacking client.
The first phase relies on the DeviceLogonEvents to determine whether an account is a service account or not, consider the following accounts with logons:.
Random_user has DeviceLogonEvents with type 2, 3, 7, 10, 11 & 13.
Random_service_account 'should' only have DeviceLogonEvents with type 3,4 or 5.
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceLogonEvents
- DeviceEvents
query: |
let InteractiveTypes = pack_array( // Declare Interactive logon type names
'Interactive',
'CachedInteractive',
'Unlock',
'RemoteInteractive',
'CachedRemoteInteractive',
'CachedUnlock'
);
let WhitelistedCmdlets = pack_array( // List of whitelisted commands that don't provide a lot of value
'prompt',
'Out-Default',
'out-lineoutput',
'format-default',
'Set-StrictMode',
'TabExpansion2'
);
let WhitelistedAccounts = pack_array('FakeWhitelistedAccount'); // List of accounts that are known to perform this activity in the environment and can be ignored
DeviceLogonEvents // Get all logon events...
| where AccountName !in~ (WhitelistedAccounts) // ...where it is not a whitelisted account...
| where ActionType == "LogonSuccess" // ...and the logon was successful...
| where AccountName !contains "$" // ...and not a machine logon.
| where AccountName !has "winrm va_" // WinRM will have pseudo account names that match this if there is an explicit permission for an admin to run the cmdlet, so assume it is good.
| extend IsInteractive=(LogonType in (InteractiveTypes)) // Determine if the logon is interactive (True=1,False=0)...
| summarize HasInteractiveLogon=max(IsInteractive) // ...then bucket and get the maximum interactive value (0 or 1)...
by AccountName // ... by the AccountNames
| where HasInteractiveLogon == 0 // ...and filter out all accounts that had an interactive logon.
// At this point, we have a list of accounts that we believe to be service accounts
// Now
| Sentinel Table | Notes |
|---|---|
DeviceEvents | Ensure this data connector is enabled |
DeviceLogonEvents | Ensure this data connector is enabled |
Scenario: Scheduled Job Running PowerShell Remotely
Description: A legitimate scheduled job uses PowerShell remoting to execute maintenance tasks across multiple servers.
Filter/Exclusion: process.parent_process_name:"schtasks.exe" or process.command_line:"schtasks /run"
Scenario: System Update via PowerShell Remoting
Description: A service account runs a PowerShell script via remoting to apply system updates across the network.
Filter/Exclusion: process.command_line:"Update-AppxProvisionedPackage" or process.command_line:"Install-WindowsUpdate"
Scenario: Remote Management via PowerShell for Troubleshooting
Description: An admin uses PowerShell remoting to troubleshoot an issue on a remote server using a service account.
Filter/Exclusion: process.parent_process_name:"Remote Desktop Connection" or process.parent_process_name:"mstsc.exe"
Scenario: Configuration Management Tool Using PowerShell Remoting
Description: A configuration management tool like Chef or Puppet uses PowerShell remoting to configure remote systems.
Filter/Exclusion: process.command_line:"Chef-client" or process.command_line:"puppet agent"
Scenario: Backup Job Using PowerShell Remoting
Description: A backup job runs a PowerShell script via remoting to back up data from multiple servers.
Filter/Exclusion: process.command_line:"Backup-System" or process.command_line:"Backup-VM"