# Microsoft Sentinel KQL Detection Pack

A starter detection pack for Microsoft Sentinel — 20 KQL queries covering identity, endpoint, cloud, email, and network with MITRE ATT&CK mapping, severity guidance, false-positive triage notes, and response actions for each. Drop into your Analytics rules library and tune to your environment. MIT licensed. Authored by VantagePoint Networks.

**Audience:** SOC Analysts, Security Engineers, IT Security Leads, MSSP Detection Engineers
**Use this when:** standing up Sentinel, expanding detection coverage, preparing detection-as-code in source control, or evaluating coverage against MITRE ATT&CK.

---

## 1. How to use this pack

1. Each detection is self-contained: query + severity + MITRE mapping + FP triage + response.
2. Tune thresholds and lookback windows to your environment before deploying.
3. Convert each into a Sentinel **Scheduled Analytics Rule** via the portal, ARM template, or `azsentinel` PowerShell.
4. Pair every rule with a runbook URL or response procedure (`/runbook-generator` skill can produce one).
5. Track FP rate per rule monthly. If FP rate > 30% sustained, retune or retire.

---

## 2. Identity detections

### 2.1 Impossible travel sign-in

```kql
SigninLogs
| where ResultType == 0
| extend City = tostring(LocationDetails.city), Country = tostring(LocationDetails.countryOrRegion)
| project TimeGenerated, UserPrincipalName, IPAddress, Country, City, AppDisplayName
| order by UserPrincipalName, TimeGenerated asc
| serialize
| extend prevTime = prev(TimeGenerated), prevCountry = prev(Country), prevCity = prev(City), prevUser = prev(UserPrincipalName)
| where UserPrincipalName == prevUser and Country != prevCountry
| extend MinutesBetween = datetime_diff('minute', TimeGenerated, prevTime)
| where MinutesBetween < 480
```

| Field | Value |
|---|---|
| Severity | High |
| MITRE | T1078 (Valid Accounts) |
| FP triage | VPN exit nodes, business travel, mobile carrier roaming. Cross-check with travel calendar / VPN logs. |
| Response | Force MFA challenge, review session token revocation, contact user, raise ticket. |

### 2.2 Brute-force lockout pattern

```kql
SigninLogs
| where ResultType in (50053, 50057, 50126)
| summarize FailedAttempts = count(), Apps = make_set(AppDisplayName) by UserPrincipalName, IPAddress, bin(TimeGenerated, 1h)
| where FailedAttempts > 20
```

| Field | Value |
|---|---|
| Severity | Medium |
| MITRE | T1110 (Brute Force) |
| FP triage | Automation hitting wrong creds, scheduled task with old password. |
| Response | Block source IP if external, force password reset, check account compromise. |

### 2.3 New admin role assignment

```kql
AuditLogs
| where OperationName has "Add member to role"
| where TargetResources has_any ("Global Administrator", "Privileged Role Administrator", "User Access Administrator", "Security Administrator")
| project TimeGenerated, InitiatedBy, TargetResources, OperationName
```

| Field | Value |
|---|---|
| Severity | Critical |
| MITRE | T1078.004 (Cloud Accounts), T1098 (Account Manipulation) |
| FP triage | Planned change with ticket; check change record. |
| Response | Confirm authorisation. If unauthorised, revoke immediately and run incident response. |

### 2.4 Sign-in from anonymising service

```kql
SigninLogs
| where RiskEventTypes has_any ("anonymizedIPAddress", "torIPAddress")
| project TimeGenerated, UserPrincipalName, IPAddress, RiskEventTypes, AppDisplayName
```

| Field | Value |
|---|---|
| Severity | High |
| MITRE | T1090 (Proxy) |
| FP triage | Privacy-conscious users on legit VPN. Cross-check with corporate VPN allow-list. |
| Response | Review session, MFA challenge, check device compliance. |

### 2.5 Service principal sudden permission grant

```kql
AuditLogs
| where OperationName == "Add app role assignment to service principal"
| extend ServicePrincipalName = tostring(TargetResources[0].displayName), Permission = tostring(TargetResources[0].modifiedProperties[1].newValue)
| where Permission has_any ("Mail.ReadWrite", "Mail.Send", "Files.ReadWrite.All", "Directory.ReadWrite.All")
```

| Field | Value |
|---|---|
| Severity | High |
| MITRE | T1098.003 (Additional Cloud Roles) |
| FP triage | Planned app onboarding. |
| Response | Confirm with app owner. If unauthorised, revoke. Common consent-grant attack vector. |

---

## 3. Endpoint detections

### 3.1 Suspicious PowerShell encoded command

```kql
DeviceProcessEvents
| where FileName =~ "powershell.exe"
| where ProcessCommandLine has_any ("-enc ", "-EncodedCommand", "-en ", "FromBase64String")
| where InitiatingProcessFileName !in~ ("Intune-Win32-App-Distribution.exe")
| project TimeGenerated, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessFileName
```

| Field | Value |
|---|---|
| Severity | High |
| MITRE | T1059.001 (PowerShell), T1027 (Obfuscated Files) |
| FP triage | Legitimate IT scripts. Maintain allow-list of known callers. |
| Response | Decode the command, check for malicious behaviour, isolate device if confirmed. |

### 3.2 Defender alert with high severity

```kql
SecurityAlert
| where ProductName == "Microsoft Defender for Endpoint"
| where AlertSeverity == "High"
| project TimeGenerated, AlertName, CompromisedEntity, Description
```

| Field | Value |
|---|---|
| Severity | High |
| MITRE | varies (use AlertName tag) |
| FP triage | Investigate the alert chain in Defender XDR; check evidence quality. |
| Response | Standard incident triage; isolate if confirmed. |

### 3.3 Mass file rename / encryption (ransomware indicator)

```kql
DeviceFileEvents
| where ActionType in ("FileRenamed", "FileCreated")
| summarize FileCount = dcount(FileName), FolderCount = dcount(FolderPath), Files = make_set(FileName, 50) by DeviceName, InitiatingProcessFileName, bin(TimeGenerated, 5m)
| where FileCount > 100
```

| Field | Value |
|---|---|
| Severity | Critical |
| MITRE | T1486 (Data Encrypted for Impact) |
| FP triage | Backup software, build pipelines, file migration tools. |
| Response | Isolate device immediately, snapshot for forensics, invoke ransomware playbook. |

### 3.4 Suspicious scheduled task creation

```kql
DeviceProcessEvents
| where FileName =~ "schtasks.exe"
| where ProcessCommandLine has "/create"
| where ProcessCommandLine has_any ("powershell", "cmd /c", "bitsadmin", "-enc")
| project TimeGenerated, DeviceName, AccountName, ProcessCommandLine
```

| Field | Value |
|---|---|
| Severity | Medium |
| MITRE | T1053.005 (Scheduled Task) |
| FP triage | IT automation. Allow-list known SYSTEM/admin creators. |
| Response | Review the task, validate purpose, remove if malicious. |

### 3.5 Living-off-the-land (LOLBin) usage

```kql
DeviceProcessEvents
| where FileName in~ ("certutil.exe", "bitsadmin.exe", "regsvr32.exe", "mshta.exe", "wscript.exe", "cscript.exe")
| where ProcessCommandLine has_any ("http://", "https://", "ftp://", "-decode", "-urlcache")
| project TimeGenerated, DeviceName, AccountName, FileName, ProcessCommandLine
```

| Field | Value |
|---|---|
| Severity | Medium |
| MITRE | T1218 (System Binary Proxy Execution), T1105 (Ingress Tool Transfer) |
| FP triage | Some IT tools use these binaries. |
| Response | Check intent of the call, block hash of any downloaded payload. |

---

## 4. Cloud (Azure / AWS)

### 4.1 Azure Activity - role assignment escalation

```kql
AzureActivity
| where OperationNameValue == "Microsoft.Authorization/roleAssignments/write"
| where ActivityStatusValue == "Success"
| extend Role = tostring(parse_json(Properties).requestbody)
| where Role has_any ("Owner", "Contributor", "User Access Administrator")
| project TimeGenerated, Caller, ResourceGroup, Role
```

| Field | Value |
|---|---|
| Severity | High |
| MITRE | T1098 (Account Manipulation), T1078.004 (Cloud Accounts) |
| FP triage | Planned IAM change. |
| Response | Confirm with caller. If unauthorised, revoke + start incident response. |

### 4.2 Azure Storage - public access enabled

```kql
AzureActivity
| where OperationNameValue endswith "storageAccounts/write"
| where ActivityStatusValue == "Success"
| extend Properties_d = parse_json(Properties)
| where tostring(Properties_d.requestbody) has '"allowBlobPublicAccess":true'
| project TimeGenerated, Caller, ResourceId
```

| Field | Value |
|---|---|
| Severity | High |
| MITRE | T1530 (Data from Cloud Storage Object) |
| FP triage | Static website hosting (legit). |
| Response | Verify intent, restrict if unintended, audit blob ACLs. |

### 4.3 Azure Firewall - unusual egress destination

```kql
AzureDiagnostics
| where Category == "AzureFirewallNetworkRule"
| where Action_s == "Allow"
| where DestinationPort_d in (4444, 6667, 8333, 9001)
| summarize Count = count() by SourceIP_s, DestinationIP_s, DestinationPort_d, bin(TimeGenerated, 1h)
| where Count > 5
```

| Field | Value |
|---|---|
| Severity | Medium |
| MITRE | T1071 (Application Layer Protocol), T1571 (Non-Standard Port) |
| FP triage | Custom apps, peer-to-peer software. |
| Response | Check if traffic is sanctioned, block if not, source-host check. |

### 4.4 AWS CloudTrail - root user activity

```kql
AWSCloudTrail
| where userIdentity_type == "Root"
| where eventName !in ("ConsoleLogin")
| project TimeGenerated, eventName, awsRegion, sourceIPAddress, userIdentity_userName
```

| Field | Value |
|---|---|
| Severity | Critical |
| MITRE | T1078.004 (Cloud Accounts) |
| FP triage | Initial setup only. After bootstrap, root should never be used. |
| Response | Investigate; revoke session if unauthorised; raise incident. |

### 4.5 Sign-in from disabled account

```kql
SigninLogs
| where ResultType == 50057 // user disabled
| summarize Attempts = count() by UserPrincipalName, IPAddress
| where Attempts > 3
```

| Field | Value |
|---|---|
| Severity | Medium |
| MITRE | T1078 (Valid Accounts) |
| FP triage | Stale device cached credentials. |
| Response | Confirm account disabled. If unexpected source, investigate as compromise attempt. |

---

## 5. Email & collaboration

### 5.1 Inbox forwarding rule created (data exfil)

```kql
OfficeActivity
| where Operation == "New-InboxRule" or Operation == "Set-InboxRule"
| extend Params = parse_json(Parameters)
| where Params has_any ("ForwardTo", "ForwardAsAttachmentTo", "RedirectTo")
| project TimeGenerated, UserId, Operation, Parameters
```

| Field | Value |
|---|---|
| Severity | High |
| MITRE | T1114.003 (Email Forwarding Rule) |
| FP triage | Holiday OOO, legitimate delegation. |
| Response | Verify with user. If not authorised, disable rule, force password reset, check session activity. |

### 5.2 Mass file download from SharePoint / OneDrive

```kql
OfficeActivity
| where RecordType in ("SharePointFileOperation")
| where Operation == "FileDownloaded"
| summarize FileCount = dcount(SourceFileName) by UserId, ClientIP, bin(TimeGenerated, 1h)
| where FileCount > 200
```

| Field | Value |
|---|---|
| Severity | High |
| MITRE | T1530 (Data from Cloud Storage Object), T1567 (Exfil Over Web) |
| FP triage | Migration projects, legitimate research. |
| Response | Verify intent, check device compliance, suspend session if suspicious. |

### 5.3 Suspicious OAuth app consent

```kql
AuditLogs
| where OperationName == "Consent to application"
| extend AppName = tostring(parse_json(tostring(TargetResources[0].modifiedProperties))[0].newValue)
| extend Permissions = tostring(parse_json(tostring(TargetResources[0].modifiedProperties))[1].newValue)
| where Permissions has_any ("Mail.ReadWrite", "Files.ReadWrite.All", "User.Read.All", "Directory.ReadWrite.All")
| project TimeGenerated, InitiatedBy, AppName, Permissions
```

| Field | Value |
|---|---|
| Severity | High |
| MITRE | T1528 (Steal Application Access Token) |
| FP triage | Legitimate vendor app onboarding. Cross-check with sanctioned-apps register. |
| Response | Confirm with admin. If unauthorised, revoke consent, investigate user account. |

---

## 6. Network

### 6.1 Failed VPN authentication burst

```kql
Syslog
| where Computer in ("vpn-fw-01", "vpn-fw-02") and ProcessName has "vpn" and SyslogMessage has_any ("authentication failed", "wrong password")
| summarize Failures = count() by SourceIP = extract("from ([0-9.]+)", 1, SyslogMessage), bin(TimeGenerated, 5m)
| where Failures > 10
```

| Field | Value |
|---|---|
| Severity | Medium |
| MITRE | T1110 (Brute Force), T1133 (External Remote Services) |
| FP triage | Stale clients with old credentials. |
| Response | Block source IP, investigate target accounts. |

### 6.2 RDP from internet

```kql
SecurityEvent
| where EventID == 4624 and LogonType == 10
| where IpAddress matches regex @"^(?!10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.).+"
| project TimeGenerated, Computer, Account, IpAddress
```

| Field | Value |
|---|---|
| Severity | Critical |
| MITRE | T1021.001 (RDP), T1133 (External Remote Services) |
| FP triage | Should never happen if RDP isn't internet-exposed. Investigate immediately. |
| Response | Network team check perimeter, isolate if compromise confirmed. |

---

## 7. Persistence / lateral movement

### 7.1 New service installed (potential persistence)

```kql
SecurityEvent
| where EventID == 7045
| project TimeGenerated, Computer, Account, ServiceName = tostring(EventData.ServiceName), ServiceFileName = tostring(EventData.ImagePath)
| where ServiceFileName has_any ("powershell", "cmd", "%temp%", "AppData", ".bat", ".vbs", ".js")
```

| Field | Value |
|---|---|
| Severity | High |
| MITRE | T1543.003 (Windows Service) |
| FP triage | Legitimate software install. Check ServiceName against vendor allow-list. |
| Response | Investigate ServiceFileName, isolate if suspicious. |

### 7.2 PsExec usage

```kql
DeviceProcessEvents
| where FileName =~ "PsExec.exe" or ProcessCommandLine has "psexec"
| project TimeGenerated, DeviceName, AccountName, ProcessCommandLine, InitiatingProcessFileName
```

| Field | Value |
|---|---|
| Severity | Medium |
| MITRE | T1021.002 (SMB / Admin Shares), T1570 (Lateral Tool Transfer) |
| FP triage | Used by IT for remote admin. Allow-list known admin sources. |
| Response | Confirm administrative purpose, otherwise investigate. |

### 7.3 Kerberoasting attempt (high TGS-REQ rate)

```kql
SecurityEvent
| where EventID == 4769
| where ServiceName !endswith "$" and TicketEncryptionType == 0x17
| summarize ServicesRequested = dcount(ServiceName), Targets = make_set(ServiceName) by TargetUserName, ClientAddress, bin(TimeGenerated, 1h)
| where ServicesRequested > 10
```

| Field | Value |
|---|---|
| Severity | High |
| MITRE | T1558.003 (Kerberoasting) |
| FP triage | Legit service with many service principals. |
| Response | Investigate source account, check for compromise. |

---

## 8. Deployment & ops

### 8.1 Convert to Sentinel rule
- Severity: as listed.
- Frequency: 5 min for endpoint / identity, 15 min for cloud / network.
- Lookback: 1 hour for high-volume rules, 24h for low-noise ones.
- Tactics: as per MITRE mapping.
- Entity mapping: User, IP, Host, FileHash where applicable.

### 8.2 Suppression (where appropriate)
- Backup software service accounts: suppress 3.3.
- Microsoft service principals: suppress relevant Azure activity.
- Known-good admin source IPs: suppress 7.2.

### 8.3 Tuning cadence
- Week 1: deploy, expect noise.
- Week 2: bulk tune top 5 noisiest.
- Month 1: monthly FP rate review.
- Quarter: re-evaluate threshold against new business activity (e.g. M&A, new regions).

### 8.4 Detection-as-code
- Store rules in a Git repo as ARM templates / Bicep / YAML.
- Pull request review by SOC + IT.
- Deploy via Sentinel REST API / Bicep.
- Tag rules with: source (this pack version), MITRE, owner, last-tuned date.

## 9. What this pack is NOT

- Not a complete SOC playbook (use the existing `runbooks/soc-playbook-bundle.md`).
- Not a substitute for Defender XDR, which has its own analytics. These rules complement, don't duplicate.
- Not zero-tune. Every rule needs your environment context.

## 10. Next-tier detections (deliberately excluded from starter pack)

- Custom UEBA models (Sentinel UEBA + KQL hybrid)
- Beaconing detection (requires longer lookback windows)
- Container escape / runtime detections (covered by Defender for Containers)
- Network metadata baselining (separate effort with NDR tooling)
- Insider threat patterns (org-specific)

These belong in a tier-2 detection pack once your team has the basic 20 stable.

## 11. References

- MITRE ATT&CK: https://attack.mitre.org/
- Microsoft Sentinel KQL reference: https://learn.microsoft.com/en-us/azure/data-explorer/kusto/query/
- Azure Sentinel community (formerly Azure-Sentinel GitHub repo) for additional vendor-contributed detections

---

**Authored by:** VantagePoint Networks (Hak, Senior Engineer & Author)
**Licence:** MIT — adapt per environment, share back improvements.
