Detection rules › Kusto
Suspicious Powershell Commandlet Executed
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
| Tactic | Techniques |
|---|---|
| Execution | T1059 Command and Scripting Interpreter |
Event coverage
| Provider | Event/ActionType | Title |
|---|---|---|
| Defender-DeviceEvents | PowerShellCommand | PowerShell command executed |
| PowerShell | Event ID 4104 | Creating Scriptblock text (MessageNumber of MessageTotal). |
Rule body kusto
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
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)
References SuspiciousPowerShellCommandList (defined above).
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
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
ActionType | eq |
|
Commandlet | match |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
DeviceName | project |
InitiatingProcessAccountUpn | project |
InitiatingProcessCommandLine | project |
InitiatingProcessFileName | project |
InitiatingProcessFolderPath | project |
InitiatingProcessId | project |
LocalIP | project |
TimeGenerated | project |
UPNSuffix | extend |
Username | extend |
DvcDomain | extend |
DvcHostname | extend |