A catalog of production-style detections for Microsoft Sentinel and Microsoft 365 Defender, each documented, tuned, and mapped to MITRE ATT&CK.
This repository is where I design, document, and tune analytic rules for cloud identity and endpoint telemetry. Every detection here started as a question (how would I see this attack in the logs?) and ended as a query I have run against real sign-in and audit data in my lab.
The goal is not a pile of queries. It is a small set of detections that each carry the context a SOC needs to act: what the threat is, why the logic catches it, what will make it misfire, and how to validate that it works. That is the difference between a query and a detection.
Each detection lives in its own folder and follows the same documentation standard, so any reviewer can open any rule and immediately understand it.
detection-engineering/
detections/
T1110.003-password-spray/
detection.kql # the analytic rule
metadata.yml # ATT&CK mapping, data source, severity
README.md # threat context, logic walkthrough, FP notes
validation.md # how to safely reproduce and confirm a hit
T1078-suspicious-signin/
T1098-mailbox-rule-abuse/
docs/
detection-standard.md # the template every rule follows
data-sources.md # tables, connectors, and retention notes
| Detection | ATT&CK Technique | Data Source | Severity | Status |
|---|---|---|---|---|
| Password spray from single source | T1110.003 | SigninLogs | High | Stable |
| Brute force against single account | T1110.001 | SigninLogs | Medium | Stable |
| Sign-in from impossible travel | T1078 | SigninLogs | Medium | In tuning |
| Suspicious inbox forwarding rule | T1098 | OfficeActivity | High | In tuning |
A query without context creates alert fatigue. Each rule in this repo ships with four things:
- Threat context. What the adversary is doing and why it matters.
- Logic walkthrough. A plain-language explanation of each clause, so the rule is maintainable by someone who did not write it.
- False positive notes. The legitimate behavior that looks similar, and how the logic accounts for it.
- Validation steps. A safe way to reproduce the activity and confirm the detection fires.
That standard lives in docs/detection-standard.md.
A password spray is the inverse of a brute force. Instead of many guesses against one account (which triggers lockout), the adversary tries one or two common passwords across many accounts to stay under per-account thresholds. The signal is not volume against a user. It is breadth: one source touching many distinct accounts in a short window.
// T1110.003 - Password Spray Detection
// Surfaces a single source failing authentication across many distinct
// accounts inside a short window, the signature of a low-and-slow spray.
let lookback = 1h;
let sprayWindow = 30m;
let accountThreshold = 10; // distinct accounts targeted from one source
SigninLogs
| where TimeGenerated > ago(lookback)
| where ResultType in (
"50126", // invalid username or password
"50053", // account locked from repeated failures
"50055" // expired password presented
)
| summarize
FailedAttempts = count(),
TargetedAccounts = dcount(UserPrincipalName),
AccountList = make_set(UserPrincipalName, 100),
ResultCodes = make_set(ResultType)
by IPAddress, bin(TimeGenerated, sprayWindow)
| where TargetedAccounts >= accountThreshold
| project TimeGenerated, IPAddress, TargetedAccounts, FailedAttempts, AccountList, ResultCodes
| order by TargetedAccounts descWhy it works. Aggregating by source IP and a time bucket, then filtering on the count of distinct accounts, isolates the spray pattern. A normal user fat-fingering a password fails against one account, not fifteen.
False positives to expect. A shared corporate NAT egress, a misconfigured application replaying stale credentials, or a security scanner can all light this up. The fix is environment-specific: maintain an allowlist of known egress IPs, or raise accountThreshold to match your tenant's baseline. The full tuning notes live in the detection folder.
Validation. Reproduce safely in a lab tenant by attempting failed logins against several test accounts from one host, then confirm the rule returns that source. Never run this against production identities you do not own.
- SIEM: Microsoft Sentinel (Log Analytics workspace)
- Identity telemetry: Entra ID sign-in and audit logs (
SigninLogs,AuditLogs) - Productivity telemetry:
OfficeActivity - Query language: KQL
- Framework: MITRE ATT&CK for technique mapping and coverage tracking
Connector setup, table schemas, and retention assumptions are documented in docs/data-sources.md.
- Build out OAuth consent-grant abuse detection (T1528)
- Add coverage tracking: an ATT&CK navigator layer showing detected vs. gaps
- Pair each detection with an Atomic Red Team test for repeatable validation
- Port stable rules into the detection-as-code pipeline
Every query here uses placeholder thresholds and lab-only data. No tenant identifiers, account names, or customer data appear in this repository. Replace accountThreshold and the egress allowlist with values baselined to your own environment before deploying.
Built and maintained by Edward Griggs, a Security+ certified systems administrator moving into detection engineering. I run identity and endpoint security for a federal contractor by day and develop detections in a self-built Sentinel and Wazuh lab. SC-200 in progress.