Master KQL query language for security analytics, build scheduled and near-real-time (NRT) detection rules, configure Fusion correlation, and implement MITRE ATT&CK-mapped detections for identity, endpoint, and cloud threats.
This lab deepens your Sentinel skills by building production-grade analytics rules using KQL. You will create detections for real-world attack scenarios including password spray, impossible travel, and MFA fatigue. Each detection is mapped to the MITRE ATT&CK framework and tuned with watchlists and threat intelligence to minimize false positives while maximizing coverage across identity, endpoint, and cloud attack surfaces.
A healthcare organization with 5,000 employees experiences credential stuffing, impossible travel, and suspicious mailbox access. Their Security Operations Center (SOC) team needs automated detections mapped to MITRE ATT&CK to identify and respond to these identity-based attacks in real time. Regulatory requirements (HIPAA, HITRUST) demand documented detection rationale and coverage mapping. The SOC must reduce mean time to detect (MTTD) from days to minutes while maintaining a manageable false positive rate across a 24/7 operation.
Detection engineering is the backbone of SOC operations. Without well-tuned analytics rules, your SIEM is just expensive log storage. Every dollar spent on Microsoft Sentinel ingestion is wasted if the data flowing through it does not generate actionable alerts. Production-grade detections require KQL proficiency, deep understanding of attack patterns, proper entity mapping for incident correlation, and continuous tuning to adapt to your environment’s baseline. This lab equips you with the skills to build, deploy, and maintain a detection library that turns raw telemetry into high-fidelity security alerts.
where, summarize, project, and extend operatorsBefore writing detection rules, you need to verify that your data sources are healthy and ingesting data. Stale or missing data will cause analytics rules to silently fail. producing zero alerts and giving you a false sense of security.
Navigate to Microsoft Sentinel β Logs and run the following KQL query to check the freshness and volume of your ingested tables:
// Data Freshness Check: Are your log sources healthy and current?
// PURPOSE: Before writing detections, verify data is actively flowing
// WHY: Stale data = silent rule failures = false sense of security
union SigninLogs, AuditLogs // Combine both identity log tables
| summarize
EventCount = count(), // Total events per table - should be >0
LastEvent = max(TimeGenerated), // Most recent event - should be within ~15 min
FirstEvent = min(TimeGenerated) // Oldest event in the workspace
by Type // Group by table name (SigninLogs vs AuditLogs)
| extend DataAge = now(). LastEvent // How stale is the data? >30 min = investigate
| project Type, EventCount, FirstEvent, LastEvent, DataAge
| order by LastEvent desc
// Expected output: Two rows showing event counts and freshness per table
// ACTION: If DataAge > 30 min, check Data connectors blade for connector healthExplore the key columns you will use in your detection queries:
// Inspect Sign-in Log Schema: Explore key columns for detection queries
// PURPOSE: Understand the data available for each authentication event
// Run this to see real values and data formats before writing detections
SigninLogs
| take 1 // Just one row - weβre exploring structure, not hunting
| project
TimeGenerated, // UTC timestamp of the sign-in attempt
UserPrincipalName, // Target account (e.g., user@contoso.com)
IPAddress, // Source IP - key for threat intel and geo lookups
Location, // String like "US" or "GB" derived from IP
ResultType, // Error code: 0=success, 50126=bad password, 50053=locked
ResultDescription, // Human-readable description of ResultType
AppDisplayName, // App being accessed (Azure Portal, Teams, Exchange, etc.)
ClientAppUsed, // Protocol: Browser, Mobile App, IMAP, SMTP, etc.
ConditionalAccessStatus, // CA policy result: success, failure, notApplied
AuthenticationRequirement, // singleFactorAuthentication or multiFactorAuthentication
MfaDetail // MFA method used and result (push, SMS, TOTP, etc.)
// Expected output: One row showing real values for each columnResultType codes like 50126 (invalid password), 50053 (locked account), and 50074 (MFA required) is critical for writing accurate detection queries.Password spraying is an attack where an adversary tries a single common password (like Spring2026!) against many user accounts, then rotates to another password. Unlike brute force (many passwords against one account), password spray stays below per-account lockout thresholds while targeting a wide surface.
The detection pattern: look for a single IP address that fails authentication against multiple distinct users within a short time window.
// Password Spray Detection
// MITRE ATT&CK: T1110.003. Brute Force: Password Spraying
SigninLogs
| where TimeGenerated > ago(1h)
| where ResultType == "50126" // Invalid username or password
| summarize
DistinctUsers = dcount(UserPrincipalName),
AttemptCount = count(),
TargetUsers = make_set(UserPrincipalName, 50),
TargetApps = make_set(AppDisplayName, 10),
FirstAttempt = min(TimeGenerated),
LastAttempt = max(TimeGenerated)
by IPAddress, bin(TimeGenerated, 1h)
| where DistinctUsers > 5
| extend AttackDuration = LastAttempt. FirstAttempt
| project
IPAddress,
DistinctUsers,
AttemptCount,
TargetUsers,
TargetApps,
FirstAttempt,
LastAttempt,
AttackDuration
| order by DistinctUsers descResultType == "50126" . Filters for failed sign-ins due to invalid credentialsdcount(UserPrincipalName) . Counts distinct targeted users per source IPmake_set() . Collects the unique usernames into a list for investigationbin(TimeGenerated, 1h) . Groups events into 1-hour windowsDistinctUsers > 5 . Threshold: alert when an IP targets more than 5 unique usersDistinctUsers > 5 threshold based on your environment. In a large enterprise with shared VPN exit IPs, you may need to raise this to 10Β·15 to avoid false positives from legitimate users behind a single NAT IP.Impossible travel occurs when a user authenticates from two geographically distant locations within a time window that makes physical travel impossible. For example, signing in from New York and then from Tokyo 30 minutes later implies credential compromise. no human can travel 10,800 km in that time.
// Impossible Travel Detection
// MITRE ATT&CK: T1078. Valid Accounts
let timeWindow = 1d;
let maxTravelSpeedKmH = 900; // Max plausible speed (commercial flight)
SigninLogs
| where TimeGenerated > ago(timeWindow)
| where ResultType == 0 // Successful sign-ins only
| where isnotempty(LocationDetails)
| extend
Latitude = toreal(LocationDetails.geoCoordinates.latitude),
Longitude = toreal(LocationDetails.geoCoordinates.longitude),
City = tostring(LocationDetails.city),
State = tostring(LocationDetails.state),
Country = tostring(LocationDetails.countryOrRegion)
| where isnotnull(Latitude) and isnotnull(Longitude)
| project
TimeGenerated, UserPrincipalName, IPAddress,
Latitude, Longitude, City, State, Country, AppDisplayName
| sort by UserPrincipalName asc, TimeGenerated asc
| serialize
| extend
PrevTime = prev(TimeGenerated, 1),
PrevLat = prev(Latitude, 1),
PrevLon = prev(Longitude, 1),
PrevIP = prev(IPAddress, 1),
PrevCity = prev(City, 1),
PrevCountry = prev(Country, 1),
PrevUser = prev(UserPrincipalName, 1)
| where UserPrincipalName == PrevUser // Same user
| where IPAddress != PrevIP // Different IPs
| extend
TimeDiffMinutes = datetime_diff('minute', TimeGenerated, PrevTime),
DistanceKm = geo_distance_2points(Longitude, Latitude, PrevLon, PrevLat) / 1000
| where DistanceKm > 500 // More than 500 km apart
| where TimeDiffMinutes > 0 // Ensure chronological order
| extend RequiredSpeedKmH = (DistanceKm / TimeDiffMinutes) * 60
| where RequiredSpeedKmH > maxTravelSpeedKmH
| project
UserPrincipalName,
PreviousSignIn = PrevTime,
PreviousIP = PrevIP,
PreviousCity = strcat(PrevCity, ", ", PrevCountry),
CurrentSignIn = TimeGenerated,
CurrentIP = IPAddress,
CurrentCity = strcat(City, ", ", Country),
TimeDiffMinutes,
DistanceKm = round(DistanceKm, 0),
RequiredSpeedKmH = round(RequiredSpeedKmH, 0)
| order by RequiredSpeedKmH descgeo_distance_2points() . Calculates the great-circle distance between two geographic coordinates in metersprev() . Accesses the previous row’s value within a serialized result setmaxTravelSpeedKmH = 900 . Accounts for commercial aviation; anything faster is impossible travelDistanceKm > 500 . Ignores short-distance moves that could be VPN or mobile network handoffsResultType == 0) are analyzed. failed attempts from distant IPs are less suspiciousserialize operator is essential here. Without it, the prev() function cannot access previous rows. This is a common pitfall when writing KQL queries that compare consecutive events for the same entity.MFA fatigue (also called MFA bombing or push exhaustion) is an attack where an adversary who already has the user’s password repeatedly triggers MFA push notifications, hoping the user will approve one out of frustration or confusion. This technique was used in the 2022 Uber breach and the 2022 Cisco breach. both high-profile incidents where MFA fatigue led to full compromise.
// MFA Fatigue Attack Detection
// MITRE ATT&CK: T1621. Multi-Factor Authentication Request Generation
SigninLogs
| where TimeGenerated > ago(1h)
| where ResultType in ("50074", "50076", "500121")
// 50074 = Strong auth required
// 50076 = MFA required (user didn't pass)
// 500121 = Authentication failed during MFA
| summarize
MFAPrompts = count(),
DistinctIPs = dcount(IPAddress),
SourceIPs = make_set(IPAddress, 10),
DistinctApps = dcount(AppDisplayName),
TargetApps = make_set(AppDisplayName, 10),
FirstPrompt = min(TimeGenerated),
LastPrompt = max(TimeGenerated)
by UserPrincipalName, bin(TimeGenerated, 10m)
| where MFAPrompts > 5
| extend BombingDuration = LastPrompt. FirstPrompt
| project
UserPrincipalName,
MFAPrompts,
DistinctIPs,
SourceIPs,
TargetApps,
FirstPrompt,
LastPrompt,
BombingDuration
| order by MFAPrompts descThis advanced query detects the worst-case scenario. an MFA fatigue attack followed by a successful authentication, indicating the user approved the malicious prompt:
// MFA Fatigue followed by successful sign-in (HIGH severity)
let MFAFatigueUsers =
SigninLogs
| where TimeGenerated > ago(1h)
| where ResultType in ("50074", "50076", "500121")
| summarize MFAPrompts = count() by UserPrincipalName, bin(TimeGenerated, 10m)
| where MFAPrompts > 5
| distinct UserPrincipalName;
SigninLogs
| where TimeGenerated > ago(1h)
| where ResultType == 0 // Successful sign-in
| where UserPrincipalName in (MFAFatigueUsers)
| project
TimeGenerated,
UserPrincipalName,
IPAddress,
AppDisplayName,
Location = strcat(LocationDetails.city, ", ", LocationDetails.countryOrRegion),
UserAgent = tostring(DeviceDetail.browser)
| order by TimeGenerated descNow that you have a validated KQL query, it’s time to operationalize it as a Sentinel analytics rule. This transforms your query from an ad-hoc investigation tool into an automated detection that continuously runs, generates alerts, and creates incidents for SOC review.
Password Spray Attack DetectedHighCredential Access β T1110.003. Brute Force: Password SprayingEnabledPaste the password spray KQL query from Step 2 into the Rule query field:
// Password Spray Detection - Analytics Rule Query
// PURPOSE: Detect when a single IP tries credentials against many users
// WHY: Password spray evades per-account lockout by spreading attempts across accounts
// MITRE ATT&CK: T1110.003 - Brute Force: Password Spraying
SigninLogs
| where ResultType == "50126" // 50126 = invalid username or password
| summarize
DistinctUsers = dcount(UserPrincipalName), // Count unique targeted accounts per IP
AttemptCount = count(), // Total failed attempts from this IP
TargetUsers = make_set(UserPrincipalName, 50), // List of targeted user accounts
TargetApps = make_set(AppDisplayName, 10), // Apps being sprayed (Portal, Exchange, etc.)
FirstAttempt = min(TimeGenerated), // Attack start time
LastAttempt = max(TimeGenerated) // Attack end time
by IPAddress, bin(TimeGenerated, 1h) // Group by source IP per 1-hour window
| where DistinctUsers > 5 // Threshold: >5 unique users = spray pattern
// Expected output: Attacker IPs with targeted user lists and attempt counts
// Entity mapping: IP β IPAddress, Account β TargetUsers1 hour1 hourgreater than 0Entity mapping connects your query results to Sentinel entities, enabling incident correlation and investigation pivot points:
Address β IPAddressFullName β TargetUsersEnabledAlerts have matching entitiesIPDisabled (for lab; enable in production)Leave this blank for now. we will configure automation playbooks in a later lab. Click Review + create, then Create.
Near-real-time (NRT) analytics rules run every minute with a 1-minute lookback, providing detection latency of approximately 1Β·2 minutes. Use NRT rules for your most critical detections where every second of delay increases blast radius.
| Feature | NRT Rule | Scheduled Rule |
|---|---|---|
| Run frequency | Every ~1 minute | 5 min Β· 14 days |
| Lookback window | Last ~1 minute | Configurable (5 min Β· 14 days) |
| Detection latency | ~1Β·2 minutes | Depends on schedule |
| KQL limitations | Single table, no joins | Full KQL supported |
| Best for | Critical, simple detections | Complex, correlation-heavy rules |
MFA Fatigue Attack Detected (NRT)HighCredential Access β T1621. Multi-Factor Authentication Request GenerationNRT rules operate on a single table with no joins. Use a simplified version of the MFA fatigue query:
// NRT Rule: MFA Fatigue Detection (runs every ~1 minute)
// PURPOSE: Near-real-time detection of MFA bombing attacks
// WHY: MFA fatigue is time-sensitive - every minute of delay = risk of user approving
// MITRE ATT&CK: T1621 - Multi-Factor Authentication Request Generation
// NRT CONSTRAINT: Single table only, no joins allowed
SigninLogs
| where ResultType in ("50074", "50076", "500121")
// 50074 = Strong auth required (MFA challenge issued)
// 50076 = User did not pass MFA (denied or timed out)
// 500121 = Authentication failed during MFA challenge
| summarize
MFAPrompts = count(), // Total MFA prompts per user in this minute
SourceIPs = make_set(IPAddress, 10), // IPs triggering the prompts
TargetApps = make_set(AppDisplayName, 10) // Apps being targeted
by UserPrincipalName // Group by victim user
| where MFAPrompts > 3 // >3 in ~1 min = aggressive MFA bombing
// Expected output: Users experiencing MFA fatigue with source IPs
// ACTION: Contact user immediately; if they approved, disable account & revoke sessionsUserPrincipalName ; IP β SourceIPsFusion is Sentinel’s built-in machine learning correlation engine that automatically detects multi-stage attacks by correlating low-fidelity alerts from multiple data sources into high-fidelity incidents. Instead of triggering on individual suspicious events, Fusion identifies attack chains. for example, a suspicious sign-in followed by privilege escalation followed by data exfiltration.
FusionFusion analyzes signals across these connected data sources:
// Review Fusion-generated incidents
// PURPOSE: Check which multi-stage attack patterns Fusion has detected
// WHY: Fusion correlates low-fidelity alerts across data sources into high-fidelity incidents
// These represent multi-step attacks (e.g., suspicious sign-in β privilege escalation β exfil)
SecurityIncident
| where ProviderName == "Azure Sentinel Fusion Engine" // Only Fusion-correlated incidents
| summarize count() by Title // Group by attack scenario type
| order by count_ desc
// Expected output: Incident titles with counts, e.g.:
// "Suspicious sign-in followed by anomalous Office 365 activity" - 3
// "Impossible travel followed by suspicious inbox rule" - 1
// No results? Enable more data connectors to give Fusion more signals to correlateThreat intelligence indicators (IOCs. Indicators of Compromise) let you match your log data against known malicious infrastructure. When a user signs in from a known-bad IP or accesses a known-malicious domain, Sentinel can automatically generate an alert.
203.0.113.50 (RFC 5737 documentation range)malicious-activity80Known C2 infrastructure from threat feedevil-example.com// View active threat intelligence indicators in your workspace
// PURPOSE: Inventory your current TI data - know what IOCs youβre matching against
// WHY: Stale or expired indicators create blind spots in threat detection
ThreatIntelligenceIndicator
| where Active == true // Only currently active indicators
| where ExpirationDateTime > now() // Filter out expired IOCs
| summarize count() by IndicatorType = tostring(parse_json(tostring(AdditionalFields)).indicatorType)
| order by count_ desc
// Expected output: Counts grouped by type (ipv4-addr, domain-name, url, file, etc.)
// ACTION: If count is 0, connect a TAXII feed or manually add IOCs in Step 8// Match sign-in IPs against threat intelligence indicators
// PURPOSE: Detect users authenticating from known-malicious IP addresses
// WHY: A sign-in from a C2 server IP = high-confidence compromise indicator
// MITRE ATT&CK: T1071 - Application Layer Protocol (C2 communication)
//
// Step 1: Build a list of known-bad IPs from your TI feed
let ThreatIPs =
ThreatIntelligenceIndicator
| where Active == true // Only active indicators
| where isnotempty(NetworkIP) // Must have an IP address value
| distinct NetworkIP; // Deduplicate for efficient matching
//
// Step 2: Find sign-ins originating from those malicious IPs
SigninLogs
| where TimeGenerated > ago(24h) // Last 24 hours of sign-in data
| where IPAddress in (ThreatIPs) // Match against TI indicator list
| project
TimeGenerated,
UserPrincipalName, // Potentially compromised account
IPAddress, // The malicious IP that matched
AppDisplayName, // What the user/attacker accessed
ResultType, // 0 = success (worst case), non-zero = blocked
Location = strcat(LocationDetails.city, ", ", LocationDetails.countryOrRegion)
| order by TimeGenerated desc
// Expected output: Sign-ins from IPs matching your threat intelligence
// ACTION: ResultType=0 β CRITICAL: account compromised, disable immediately
// ACTION: ResultTypeβ 0 β blocked attempt, monitor for persistenceFor production environments, connect automated threat intelligence feeds using the TAXII protocol:
Watchlists are reusable reference tables in Sentinel that you can join into your KQL queries. They are ideal for maintaining lists of VIP users, known-good IPs, service accounts, or any enrichment data that your analytics rules need to reference.
VIPUsersVIPUsersUserPrincipalName,DisplayName,Department,RiskLevel
ceo@contoso.com,Jane Smith,Executive,Critical
cfo@contoso.com,John Doe,Finance,Critical
ciso@contoso.com,Alex Johnson,Security,Critical
vp-engineering@contoso.com,Sarah Lee,Engineering,High
admin-global@contoso.com,IT Admin,IT,CriticalUserPrincipalName as the primary lookup column// View all entries in the VIPUsers watchlist
// PURPOSE: Verify watchlist contents before using it in analytics rules
// WHY: Incorrect watchlist data causes missed detections or false positives
_GetWatchlist('VIPUsers') // Built-in function to query a Sentinel watchlist
| project UserPrincipalName, DisplayName, Department, RiskLevel
// Expected output: Table of VIP users as defined in your uploaded CSV
// These users receive enhanced monitoring with lower alert thresholdsEnhance your password spray detection to flag VIP-targeted attacks at higher severity:
// Password spray targeting VIP users (escalated severity)
// PURPOSE: Detect password spray attempts specifically against high-value accounts
// WHY: VIPs (C-suite, admins) have elevated access - compromise = maximum blast radius
// Use a LOWER threshold (>3 vs >5) because any attack on VIPs warrants investigation
let VIPUsers = _GetWatchlist('VIPUsers') | project UserPrincipalName; // Load VIP list
SigninLogs
| where ResultType == "50126" // 50126 = invalid password attempt
| where UserPrincipalName in (VIPUsers) // Only VIP accounts
| summarize
AttemptCount = count(), // Failed attempts against this VIP
SourceIPs = make_set(IPAddress, 10), // Attacker source IPs
FirstAttempt = min(TimeGenerated), // Attack window start
LastAttempt = max(TimeGenerated) // Attack window end
by UserPrincipalName, bin(TimeGenerated, 1h) // Group per VIP per hour
| where AttemptCount > 3 // Lower threshold for VIPs (3 vs 5)
| project
UserPrincipalName,
AttemptCount,
SourceIPs,
FirstAttempt,
LastAttempt
// Expected output: VIP accounts under credential attack with source IPs
// ACTION: Immediately alert CISO; consider proactive password reset for targeted VIPsReduce impossible travel false positives by maintaining a list of known corporate VPN egress IPs:
// Impossible travel with VPN allowlist - reduce false positives
// PURPOSE: Exclude known corporate VPN egress IPs from impossible travel detection
// WHY: Users on VPN appear to sign in from the VPN serverβs location, not their own
// This causes false impossible travel alerts when users switch between VPN and direct
let VPNIPs = _GetWatchlist('CorporateVPNIPs') | project IPAddress; // Load VPN IP allowlist
SigninLogs
| where ResultType == 0 // Only successful sign-ins (same as travel query)
| where IPAddress !in (VPNIPs) // Exclude known VPN egress IPs
// ... rest of impossible travel query // Continue with geo-distance calculationThe MITRE ATT&CK framework provides a standardized taxonomy of adversary tactics and techniques. Mapping your detections to ATT&CK gives your SOC team a structured way to measure detection coverage, identify gaps, and communicate threat posture to leadership.
| Detection | Tactic | Technique | ID |
|---|---|---|---|
| Password Spray | Credential Access | Brute Force: Password Spraying | T1110.003 |
| Impossible Travel | Initial Access | Valid Accounts | T1078 |
| MFA Fatigue | Credential Access | MFA Request Generation | T1621 |
| Privilege Escalation | Privilege Escalation | Account Manipulation | T1098 |
| Suspicious Inbox Rule | Collection | Email Collection | T1114 |
| OAuth App Consent | Persistence | Application Access Token | T1550.001 |
| Token Theft | Credential Access | Steal Application Access Token | T1528 |
// Review analytics rules and their MITRE ATT&CK mappings
// PURPOSE: Programmatically identify which rules are tagged with MITRE techniques
// WHY: Helps validate that all rules have proper ATT&CK classification
SentinelAudit
| where SentinelResourceType == "Analytic Rule" // Only analytics rule changes
| where Description has "MITRE" or Description has "ATT&CK" // Filter for ATT&CK-tagged rules
| project RuleName = SentinelResourceName, Description
| distinct RuleName, Description // Deduplicate
// Expected output: List of rule names with their MITRE-tagged descriptions
// ACTION: Cross-reference with the ATT&CK blade to find untagged rulesCreating detection rules is only half the job. Production-grade detections require continuous tuning to balance detection coverage (catching real attacks) against false positive rate (not drowning the SOC in noise). A rule that generates 200 alerts per day will be ignored by analysts. effectively making it useless.
// Alert volume by rule over the last 7 days
// PURPOSE: Identify noisy rules and tune thresholds for optimal SOC workload
// WHY: Rules generating >20 alerts/day overwhelm analysts and get ignored
// Target: 1-10 meaningful alerts per rule per day
SecurityAlert
| where TimeGenerated > ago(7d) // Look back one full week for trends
| where ProviderName == "Azure Sentinel" // Only Sentinel-generated alerts (not Defender)
| summarize
AlertCount = count(), // Total alerts from this rule in 7 days
DistinctDays = dcount(bin(TimeGenerated, 1d)) // How many days it fired on
by AlertName // Group by rule name
| extend AvgAlertsPerDay = round(AlertCount * 1.0 / DistinctDays, 1) // Daily average
| order by AlertCount desc
// Expected output: Table of rules ranked by total alerts
// ACTION: AvgAlertsPerDay > 20? β raise thresholds, add watchlist exclusions
// ACTION: AvgAlertsPerDay = 0 for 7+ days? β lower thresholds or verify data sourceSuppression prevents the same rule from generating duplicate alerts within a specified time window:
5 hours for the password spray rule)A detection library is a living catalog of all your analytics rules, their purpose, tuning history, and operational status. Maintaining this document is critical for SOC knowledge transfer, audit compliance, and detection lifecycle management.
Export your analytics rules as ARM templates for version control and deployment across environments:
# Export all analytics rules using Azure CLI
# PURPOSE: Back up your detection library for version control and cross-environment deployment
# --output json : Full JSON export including KQL queries, entity mappings, and MITRE tags
# Pipe to a file in your Git repo for infrastructure-as-code management
az sentinel alert-rule list \
--resource-group rg-sentinel-lab \
--workspace-name law-sentinel-lab \
--output json > sentinel-rules-export.json
# Expected output: JSON file containing all analytics rules with full configuration
# ACTION: Commit to Git and use CI/CD to deploy across dev/staging/prod workspacesFor each analytics rule, document the following:
Use a standardized template for your detection catalog. Here’s a summary of what we built in this lab:
| # | Rule Name | Type | MITRE | Severity |
|---|---|---|---|---|
| 1 | Password Spray Attack Detected | Scheduled | T1110.003 | High |
| 2 | Impossible Travel Detected | Scheduled | T1078 | Medium |
| 3 | MFA Fatigue Attack Detected (NRT) | NRT | T1621 | High |
| 4 | MFA Fatigue Followed by Success | Scheduled | T1621 | Critical |
| 5 | Threat Intel IP Match | Scheduled | T1071 | High |
| 6 | VIP Account Password Spray | Scheduled | T1110.003 | Critical |
Congratulations! In this lab you have:
| Resource | Description |
|---|---|
| KQL quick reference | Complete reference for Kusto Query Language operators, functions, and syntax |
| Analytics rules best practices | Create custom scheduled query rules to detect threats in Sentinel |
| Near-real-time (NRT) analytics rules | Deploy and manage NRT detection rules |
| Fusion detection | Advanced multistage attack detection powered by machine learning |
| Threat intelligence in Sentinel | Import, manage, and use threat intelligence indicators |
| Watchlists | Create and manage reusable reference data in Sentinel |
| MITRE ATT&CK framework | Adversary tactics, techniques, and procedures knowledge base |
| Entra ID authentication error codes | Complete list of ResultType codes for sign-in log analysis |