55 строки
4.5 KiB
Plaintext
55 строки
4.5 KiB
Plaintext
// 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
|
|
//
|
|
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
|