Azure VM Run Command linked with MDE


Description

'Identifies any Azure VM Run Command operations and links these operations with MDE host logging. Linking these two data sources provides hunting opportunities. Logging from AzureActivity provides the IP address and UPN of the account that invoked the command. Joining this with logging from MDE provides insights into what cmdlets were loaded by the command.'

Query · kql

AzureActivity
// Isolate run command actions
| where OperationNameValue == "Microsoft.Compute/virtualMachines/runCommand/action"
// Confirm that the operation impacted a virtual machine
| where Authorization has "virtualMachines"
// Each runcommand operation consists of three events when successful, Started, Accepted (or Rejected), Successful (or Failed).
| summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), max(CallerIpAddress), make_list(ActivityStatusValue) by CorrelationId, Authorization, Caller
// Limit to Run Command executions that Succeeded
| where list_ActivityStatusValue has "Succeeded"
// Extract data from the Authorization field, allowing us to later extract the Caller (UPN) and CallerIpAddress
| extend Authorization_d = parse_json(Authorization)
| extend Scope = Authorization_d.scope
| extend Scope_s = split(Scope, "/")
| extend Subscription = tostring(Scope_s[2])
| extend VirtualMachineName = tostring(Scope_s[-1])
| project StartTime, EndTime, Subscription, VirtualMachineName, CorrelationId, Caller, CallerIpAddress=max_CallerIpAddress
| join kind=leftouter (
    DeviceFileEvents
    | where InitiatingProcessFileName == "RunCommandExtension.exe"
    | extend VirtualMachineName = tostring(split(DeviceName, ".")[0])
    | project VirtualMachineName, PowershellFileCreatedTimestamp=TimeGenerated, FileName, FileSize, InitiatingProcessAccountName, InitiatingProcessAccountDomain, InitiatingProcessFolderPath, InitiatingProcessId
) on VirtualMachineName
// We need to filter by time sadly, this is the only way to link events
| where PowershellFileCreatedTimestamp between (StartTime .. EndTime)
| project StartTime, EndTime, PowershellFileCreatedTimestamp, VirtualMachineName, Caller, CallerIpAddress, FileName, FileSize, InitiatingProcessId, InitiatingProcessAccountDomain, InitiatingProcessFolderPath
| join kind=inner(
    DeviceEvents
    | extend VirtualMachineName = tostring(split(DeviceName, ".")[0])
    | where InitiatingProcessCommandLine has "-File"
    | extend PowershellFileName = extract(@"\-File\s(script[0-9]{1,9}\.ps1)", 1, InitiatingProcessCommandLine)
    | extend PSCommand = tostring(parse_json(AdditionalFields).Command)
    | order by TimeGenerated asc 
    | where PSCommand != PowershellFileName 
    | summarize PowershellExecStart=min(TimeGenerated), PowershellExecEnd=max(TimeGenerated), make_list(PSCommand) by PowershellFileName, InitiatingProcessCommandLine
) on $left.FileName == $right.PowershellFileName
| project StartTime, EndTime, PowershellFileCreatedTimestamp, PowershellExecStart, PowershellExecEnd, PowershellFileName, PowershellScriptCommands=list_PSCommand, Caller, CallerIpAddress, InitiatingProcessCommandLine, PowershellFileSize=FileSize, VirtualMachineName
| order by StartTime asc 
| extend ScriptFingerprintHash = hash_sha256(tostring(PowershellScriptCommands))
Raw source Azure VM Run Command linked with MDE · KQL
Esc
Published by Azure/Azure-Sentinel ↗, licensed under MIT ↗. Reproduced here unmodified.
id: 55fbc363-6cc9-4201-bd68-d980b612082b
name: Azure VM Run Command linked with MDE
description: |
  'Identifies any Azure VM Run Command operations and links these operations with
  MDE host logging. Linking these two data sources provides hunting opportunities.
  Logging from AzureActivity provides the IP address and UPN of the account that
  invoked the command. Joining this with logging from MDE provides insights into
  what cmdlets were loaded by the command.'
requiredDataConnectors:
  - connectorId: AzureActivity
    dataTypes:
      - AzureActivity
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceFileEvents
      - DeviceEvents
tactics:
  - LateralMovement
  - CredentialAccess
relevantTechniques:
  - T1570
  - T1078.004
query: |
  AzureActivity
  // Isolate run command actions
  | where OperationNameValue == "Microsoft.Compute/virtualMachines/runCommand/action"
  // Confirm that the operation impacted a virtual machine
  | where Authorization has "virtualMachines"
  // Each runcommand operation consists of three events when successful, Started, Accepted (or Rejected), Successful (or Failed).
  | summarize StartTime=min(TimeGenerated), EndTime=max(TimeGenerated), max(CallerIpAddress), make_list(ActivityStatusValue) by CorrelationId, Authorization, Caller
  // Limit to Run Command executions that Succeeded
  | where list_ActivityStatusValue has "Succeeded"
  // Extract data from the Authorization field, allowing us to later extract the Caller (UPN) and CallerIpAddress
  | extend Authorization_d = parse_json(Authorization)
  | extend Scope = Authorization_d.scope
  | extend Scope_s = split(Scope, "/")
  | extend Subscription = tostring(Scope_s[2])
  | extend VirtualMachineName = tostring(Scope_s[-1])
  | project StartTime, EndTime, Subscription, VirtualMachineName, CorrelationId, Caller, CallerIpAddress=max_CallerIpAddress
  | join kind=leftouter (
      DeviceFileEvents
      | where InitiatingProcessFileName == "RunCommandExtension.exe"
      | extend VirtualMachineName = tostring(split(DeviceName, ".")[0])
      | project VirtualMachineName, PowershellFileCreatedTimestamp=TimeGenerated, FileName, FileSize, InitiatingProcessAccountName, InitiatingProcessAccountDomain, InitiatingProcessFolderPath, InitiatingProcessId
  ) on VirtualMachineName
  // We need to filter by time sadly, this is the only way to link events
  | where PowershellFileCreatedTimestamp between (StartTime .. EndTime)
  | project StartTime, EndTime, PowershellFileCreatedTimestamp, VirtualMachineName, Caller, CallerIpAddress, FileName, FileSize, InitiatingProcessId, InitiatingProcessAccountDomain, InitiatingProcessFolderPath
  | join kind=inner(
      DeviceEvents
      | extend VirtualMachineName = tostring(split(DeviceName, ".")[0])
      | where InitiatingProcessCommandLine has "-File"
      | extend PowershellFileName = extract(@"\-File\s(script[0-9]{1,9}\.ps1)", 1, InitiatingProcessCommandLine)
      | extend PSCommand = tostring(parse_json(AdditionalFields).Command)
      | order by TimeGenerated asc 
      | where PSCommand != PowershellFileName 
      | summarize PowershellExecStart=min(TimeGenerated), PowershellExecEnd=max(TimeGenerated), make_list(PSCommand) by PowershellFileName, InitiatingProcessCommandLine
  ) on $left.FileName == $right.PowershellFileName
  | project StartTime, EndTime, PowershellFileCreatedTimestamp, PowershellExecStart, PowershellExecEnd, PowershellFileName, PowershellScriptCommands=list_PSCommand, Caller, CallerIpAddress, InitiatingProcessCommandLine, PowershellFileSize=FileSize, VirtualMachineName
  | order by StartTime asc 
  | extend ScriptFingerprintHash = hash_sha256(tostring(PowershellScriptCommands))
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: AccountCustomEntity
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IPCustomEntity

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.