// Illustrative; not run against live data. Microsoft Sentinel, Syslog table fed by your ESXi hosts. // Shell access switched on, protections changed, VMs killed and snapshots removed: the steps hypervisor ransomware needs. // HostsInWindow counts how many hosts did any of this in the same 10 minutes. Many hosts at once is a ransomware run, not maintenance. let Since = ago(14d); let EsxiHosts = @"(?i)REPLACE_WITH_ESXI_HOSTNAME_PATTERN"; let Risky = @"(?i)(execInstalledOnly|esxcli\s+vm\s+process\s+(list|kill)|vim-cmd\s+vmsvc/(getallvms|power\.off|power\.shutdown|snapshot\.removeall|snapshot\.remove)|vim-cmd\s+hostsvc/(enable_ssh|start_ssh|enable_esx_shell|start_esx_shell)|esxcli\s+system\s+(syslog\s+config\s+set|account\s+(add|set)|permission\s+set|welcomemsg\s+set)|esxcli\s+network\s+firewall\s+set|chmod\s+\+?[0-7x]+\s+/(tmp|vmfs/volumes)/|SSH access has been enabled|ESXi Shell access has been enabled)"; let Hits = Syslog | where TimeGenerated > Since | extend Host = coalesce(HostName, Computer) | where Host matches regex EsxiHosts | where SyslogMessage matches regex Risky | extend Window = bin(TimeGenerated, 10m), Action = tolower(extract(Risky, 1, SyslogMessage)); Hits | summarize Events = count(), Actions = make_set(Action, 20), Samples = make_set(SyslogMessage, 5), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by Host, Window | join kind=inner (Hits | summarize HostsInWindow = dcount(Host) by Window) on Window | project-away Window1 | order by HostsInWindow desc, FirstSeen asc | take 500