I triaged this pattern hundreds of times. Here's the KQL that actually works (with noise reduction built in)
Source Reddit BlueTeam
Discussion
This content describes a practical detection approach for identifying encoded PowerShell commands using Kusto Query Language (KQL) in Microsoft Sentinel. The detection method incorporates a weighted scoring model that reduces noise by considering multiple indicators beyond the simple presence of encoded commands, such as the parent process, download-related keywords, execution flags, and execution paths. The approach aims to improve alert accuracy and reduce false positives in enterprise environments. It is not a vulnerability or exploit but a detection technique shared on a public forum.
r/blueteamsec (opens in a new tab)u/Ok_Attitude92640 points0 comments
Most encoded PowerShell detections I see shared online look like this:
SecurityEvent
| where EventID == 4688
| where CommandLine has "-enc"
Deploy that in a real enterprise and you'll get 100-200 hits a day, 95% of which are SCCM, Tanium, or Microsoft's own tooling. Analysts learn to ignore it within two weeks. That's how real detections get missed.
After a decade in SOC operations I built a weighted scoring model for this instead. The encoding flag alone scores zero. It's the combination of indicators that triggers the alert:
**Score modifiers:**
- +30: Office app or browser spawned PowerShell (Word, Excel, Outlook, Chrome, Edge)
- +25: Download cradle keywords in command line (DownloadString, WebClient, IWR, wget)
- +20: Execution from user-writable path (\Temp\, \AppData\, \Downloads\)
- +15: Hidden window flag (-WindowStyle Hidden)
- +15: Execution policy bypass (-ExecutionPolicy Bypass)
- +10: Non-interactive / no-profile flags
A legitimate SCCM script that happens to use -enc scores 0. A phishing-delivered payload that spawns from Outlook with a download cradle and hidden window scores 70. You set the threshold at 30 or 40 and the noise drops dramatically.
**Full production KQL in the article** (too long to paste here in full, but the core structure is):
let BenignParents = dynamic(["taniumclient.exe","ccmexec.exe","devenv.exe","msbuild.exe"]);
let HighRiskParents = dynamic(["winword.exe","excel.exe","outlook.exe","chrome.exe","msedge.exe"]);
let DownloadCradles = dynamic(["downloadstring","webclient","invoke-webrequest","iwr"]);
SecurityEvent
| where EventID == 4688
| where NewProcessName endswith "\\powershell.exe"
| where CommandLine matches regex @'(?i)-e[nN]?[cC]([oO][dD][eE][dD][cC][oO][mM][mM][aA][nN][dD])?[\s:]'
| where not(ParentProcessName has_any (BenignParents))
| extend Score = 0
| extend Score = Score + iff(ParentName in~ (HighRiskParents), 30, 0)
| extend Score = Score + iff(CmdLower has_any (DownloadCradles), 25, 0)
// ... (continues with remaining modifiers)
| extend AlertSeverity = case(Score >= 60, "Critical", Score >= 40, "High", Score >= 20, "Medium", "Low")
| where Score >= 20
**A few things I've learned deploying this:**
Build your BenignParents allowlist before deploying. Run the basic detection against 7 days of logs, export ParentName, add anything appearing 20+ times that's legitimate in your environment.
Start threshold at 30 in noisy environments. Tune down as you build confidence.
Set alert grouping by Computer in Sentinel with a 24h window or you'll get 20 identical incidents instead of one.
PowerShell 2.0 is a blind spot. Add a separate rule for `-version 2` combined with encoding flags — no script block logging at all in v2.
When this fires High or Critical: decode the Base64 first before isolating. You need to know if it's fileless before you pull the network cable.
MITRE: T1059.001 + T1027. When combined with an Office parent, the chain is T1566.001 → T1204.002 → T1059.001 → T1105.
Full article with complete KQL, scoring table, and triage steps: socauthority.com/blog/how-to-detect-powershell-encoded-commands-sentinel-kql/
Happy to answer questions or discuss tuning approaches for specific environments.