Detection rules › Kusto

Suspicious Powershell Commandlet Executed

Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

This analytic rule detects when a suspicious PowerShell commandlet is executed on a host. Threat actors often use PowerShell to execute commands and scripts to move laterally, escalate privileges, and exfiltrate data.

MITRE ATT&CK coverage

Telemetry coverage

Rule body

id: b5153fb3-ada9-4ce4-9131-79c771efb50d
name: Suspicious Powershell Commandlet Executed 
description: |
  This analytic rule detects when a suspicious PowerShell commandlet is executed on a host. Threat actors often use PowerShell to execute commands and scripts to move laterally, escalate privileges, and exfiltrate data.
severity: Medium
status: Available 
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
relevantTechniques:
  - T1059
query: |
  // Adjust the list of suspicious commandlets as needed
  let SuspiciousPowerShellCommandList = dynamic(["Get-ADUserResultantPasswordPolicy",
    "Get-DomainPolicy",
    "Get-DomainUser",
    "Get-DomainComputer",
    "Get-DomainController",
    "Get-DomainGroup",
    "Get-DomainTrust",
    "Get-ADTrust",
    "Get-ForestTrust"
    ]);
  DeviceEvents
  | where ActionType == "PowerShellCommand"
  | extend Commandlet = tostring(parse_json(AdditionalFields).Command)
  | where Commandlet has_any (SuspiciousPowerShellCommandList)
  | project TimeGenerated, DeviceName, LocalIP, InitiatingProcessAccountUpn, InitiatingProcessId, InitiatingProcessFileName, InitiatingProcessFolderPath, InitiatingProcessCommandLine
  | extend Username = tostring(split(InitiatingProcessAccountUpn, '@')[0]), UPNSuffix = tostring(split(InitiatingProcessAccountUpn, '@')[1])
  | extend DvcHostname = tostring(split(DeviceName, '.')[0]), DvcDomain = tostring(strcat_array(array_slice(split(DeviceName, '.'), 1, -1), '.'))
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
      - identifier: HostName
        columnName: DvcHostname
      - identifier: DnsDomain
        columnName: DvcDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: LocalIP
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: Username
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: Process
    fieldMappings:
      - identifier: ProcessId
        columnName: InitiatingProcessId
      - identifier: CommandLine
        columnName: InitiatingProcessCommandLine
eventGroupingSettings:
  aggregationKind: AlertPerResult
alertDetailsOverride:
  alertDisplayNameFormat: "Suspicious PowerShell Commandlet Executed on {{DvcHostname}} ({{LocalIP}}) by ({{InitiatingProcessAccountUpn}})"
  alertDescriptionFormat: "Suspicious PowerShell Commandlet by Process '{{InitiatingProcessFileName}}' ProcessId: '{{InitiatingProcessId}}' with commandline {{InitiatingProcessCommandLine}} was executed."
version: 1.0.1
kind: Scheduled

Stages and Predicates

Let binding: SuspiciousPowerShellCommandList used in Stage 4

let SuspiciousPowerShellCommandList = dynamic(["Get-ADUserResultantPasswordPolicy",
  "Get-DomainPolicy",
  "Get-DomainUser",
  "Get-DomainComputer",
  "Get-DomainController",
  "Get-DomainGroup",
  "Get-DomainTrust",
  "Get-ADTrust",
  "Get-ForestTrust"
  ]);

Stage 1: source

DeviceEvents

Stage 2: where

| where ActionType == "PowerShellCommand"

Stage 3: extend

| extend Commandlet = tostring(parse_json(AdditionalFields).Command)

Stage 4: where

| where Commandlet has_any (SuspiciousPowerShellCommandList)

Stage 5: project

| project TimeGenerated, DeviceName, LocalIP, InitiatingProcessAccountUpn, InitiatingProcessId, InitiatingProcessFileName, InitiatingProcessFolderPath, InitiatingProcessCommandLine

Stage 6: extend

| extend Username = tostring(split(InitiatingProcessAccountUpn, '@')[0]), UPNSuffix = tostring(split(InitiatingProcessAccountUpn, '@')[1])

Stage 7: extend

| extend DvcHostname = tostring(split(DeviceName, '.')[0]), DvcDomain = tostring(strcat_array(array_slice(split(DeviceName, '.'), 1, -1), '.'))

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActionTypeeq
  • PowerShellCommand corpus 2 (kusto 2)
field:"ActionType" kind:eq value:"PowerShellCommand"
Commandletmatch
  • Get-ADTrust transforms: term
  • Get-ADUserResultantPasswordPolicy transforms: term
  • Get-DomainComputer transforms: term
  • Get-DomainController transforms: term
  • Get-DomainGroup transforms: term
  • Get-DomainPolicy transforms: term
  • Get-DomainTrust transforms: term
  • Get-DomainUser transforms: term
  • Get-ForestTrust transforms: term
field:"Commandlet" kind:match

Output fields

These fields are emitted when the rule matches.

FieldSource
DeviceNameproject
InitiatingProcessAccountUpnproject
InitiatingProcessCommandLineproject
InitiatingProcessFileNameproject
InitiatingProcessFolderPathproject
InitiatingProcessIdproject
LocalIPproject
TimeGeneratedproject
UPNSuffixextend
Usernameextend
DvcDomainextend
DvcHostnameextend