📑 Table of Contents

Microsoft Sentinel is a cloud-native SIEM (Security Information and Event Management) and SOAR (Security Orchestration, Automation, and Response) solution. This lab focuses on leveraging Sentinel for identity security—detecting compromised accounts, insider threats, privilege abuse, and automating incident response for IAM-related threats.

🎯 Lab Overview & Sentinel Architecture

Microsoft Sentinel provides intelligent security analytics across your enterprise, with special capabilities for identity-based threat detection through integration with Entra ID, Microsoft 365, and cloud platforms.

📖 Sentinel Core Components

ComponentPurpose
Data ConnectorsIngest logs from Entra ID, M365, Azure, AWS, firewalls, etc.
Log Analytics WorkspaceStore and query security data using KQL
Analytics RulesDetect threats and create incidents automatically
UEBABehavioral analytics to detect anomalous user/entity activity
IncidentsCorrelated alerts for investigation and response
PlaybooksLogic Apps for automated response (SOAR)
WorkbooksInteractive dashboards and reports
HuntingProactive threat hunting with custom queries

▼ SENTINEL IDENTITY SECURITY ARCHITECTURE ▼

Data Sources

Entra ID | Microsoft 365 | Azure Activity | AWS CloudTrail | On-prem AD

Data Connectors

Native connectors, CEF/Syslog, REST API, Azure Functions

Log Analytics Workspace

SigninLogs | AuditLogs | SecurityEvent | AADUserRiskEvents

Detection & Analytics

Scheduled Rules | NRT Rules | Fusion | UEBA | ML

Response & Investigation

Incidents | Playbooks | Entity Pages | Investigation Graph

Identity-Related Log Tables

Key Tables for Identity Security: SigninLogs ├── User sign-in events ├── MFA status, Conditional Access results ├── Location, device, application info └── Risk level (Identity Protection) AADNonInteractiveUserSignInLogs ├── Service principal sign-ins ├── Background authentication └── Automated processes AuditLogs ├── Directory changes (users, groups, roles) ├── Application changes ├── PIM activations └── Conditional Access policy changes AADUserRiskEvents ├── Identity Protection detections ├── Leaked credentials ├── Impossible travel └── Anonymous IP, malware-linked IP AADRiskyUsers ├── Users flagged as risky ├── Risk level (low, medium, high) └── Risk state (at risk, dismissed, remediated) IdentityInfo (UEBA) ├── Entity profiles ├── Group memberships ├── Account attributes └── Behavioral baseline BehaviorAnalytics (UEBA) ├── Anomalous activities ├── Deviation from baseline └── Investigation priority score

🔌 Module 1: Workspace Setup & Data Connectors

Module 1: Deploy Sentinel and Connect Identity Sources

Set up Microsoft Sentinel and configure data connectors for identity logs.

⏱️ 45-60 minutes🎯 5 steps
1

Create Log Analytics Workspace

Azure Portal
# Create Log Analytics Workspace via CLI az monitor log-analytics workspace create \ --resource-group rg-security \ --workspace-name law-sentinel-prod \ --location eastus \ --retention-time 90 \ --sku PerGB2018 # Or via Portal: # Azure Portal → Log Analytics workspaces → Create # - Subscription: Select subscription # - Resource group: rg-security # - Name: law-sentinel-prod # - Region: East US (or your region) # → Review + Create # Retention Settings: # - Default: 30 days (free) # - Recommended: 90-365 days for security # - Compliance may require longer
2

Enable Microsoft Sentinel

Sentinel
Navigate to: Azure Portal → Microsoft Sentinel Add Sentinel to Workspace: → Click "Add" → Select your Log Analytics workspace (law-sentinel-prod) → Click "Add" Initial Setup: - Sentinel solution deployed on workspace - Content hub available - Data connectors gallery enabled Pricing: - Pay-per-GB ingested - Commitment tiers available (100GB+) - Free tier: 10GB/day first 31 days First 90 Days: - Microsoft 365 E5 includes 5GB/user/month - Entra ID P2 logs free to ingest - Plan capacity based on log volume
3

Connect Entra ID (Azure AD) Logs

Data Connector
Navigate to: Sentinel → Data connectors → Search "Microsoft Entra ID" → Open connector page Configure Connection: SIGN-IN LOGS: ☑️ Azure AD Sign-in logs - Contains: All user sign-ins - Table: SigninLogs ☑️ Azure AD Non-Interactive Sign-in logs - Contains: Service principal, background auth - Table: AADNonInteractiveUserSignInLogs ☑️ Azure AD Service Principal Sign-in logs - Contains: App/service authentications - Table: AADServicePrincipalSignInLogs ☑️ Azure AD Managed Identity Sign-in logs - Contains: Managed identity authentications - Table: AADManagedIdentitySignInLogs AUDIT LOGS: ☑️ Azure AD Audit logs - Contains: Directory changes, PIM, CA - Table: AuditLogs ☑️ Azure AD Provisioning logs - Contains: User provisioning events - Table: AADProvisioningLogs → Apply Changes Verify: Run query after 5-10 minutes SigninLogs | take 10
4

Connect Microsoft 365 & Identity Protection

Data Connector
Microsoft 365 Defender Connector: → Data connectors → Microsoft 365 Defender Enable: ☑️ Microsoft Defender for Identity - On-premises AD monitoring - Lateral movement detection - Table: IdentityLogonEvents, IdentityDirectoryEvents ☑️ Microsoft Defender for Cloud Apps - Cloud app usage - Shadow IT - Table: CloudAppEvents ☑️ Microsoft Defender for Office 365 - Email threats - Phishing attempts - Table: EmailEvents --- Identity Protection (Entra ID P2): → Data connectors → Azure AD Identity Protection Enable: ☑️ Security Alerts - Risk detections - Table: SecurityAlert (filtered) ☑️ Risky Users - Users flagged as risky - Table: AADRiskyUsers ☑️ User Risk Events - Individual risk events - Table: AADUserRiskEvents
5

Essential Data Connectors for Identity

Connectors
🔷
Microsoft Entra ID

Sign-in & audit logs, provisioning

🛡️
Identity Protection

Risk events, risky users

🔐
Defender for Identity

On-prem AD monitoring

☁️
Azure Activity

Azure resource operations

📧
Office 365

Exchange, SharePoint, Teams

🌐
AWS CloudTrail

AWS IAM activity

🔑
Okta SSO

Okta sign-in events

🏢
Windows Security

Domain controller events

📊 Module 2: KQL for Identity Hunting

Module 2: Master KQL for Identity Security Queries

Learn Kusto Query Language (KQL) for identity threat hunting.

⏱️ 60-90 minutes🎯 6 steps
6

KQL Fundamentals

KQL Basics
// KQL Query Structure TableName | where TimeGenerated > ago(24h) // Filter by time | where Column == "Value" // Filter rows | project Column1, Column2, Column3 // Select columns | summarize count() by Column1 // Aggregate | order by count_ desc // Sort | take 10 // Limit results // Common Operators | where // Filter rows | project // Select/rename columns | extend // Add calculated columns | summarize // Aggregate (count, sum, avg, etc.) | join // Combine tables | union // Append tables | order by // Sort results | take / limit // Limit rows | render // Visualize (timechart, piechart, etc.) // Time Functions ago(1h) // 1 hour ago ago(7d) // 7 days ago ago(30d) // 30 days ago now() // Current time startofday() // Start of today between(datetime..datetime) // String Functions contains // Case-insensitive contains has // Word boundary match (faster) startswith // Starts with matches regex // Regex match tolower() // Lowercase tostring() // Convert to string
7

Sign-In Analysis Queries

KQL
// Failed Sign-ins by User (Last 24 Hours) SigninLogs | where TimeGenerated > ago(24h) | where ResultType != 0 // Non-success | summarize FailedCount = count(), DistinctIPs = dcount(IPAddress), Apps = make_set(AppDisplayName) by UserPrincipalName | where FailedCount > 10 | order by FailedCount desc // Sign-ins from New Countries (First Time Ever) let KnownCountries = SigninLogs | where TimeGenerated between (ago(90d) .. ago(1d)) | distinct UserPrincipalName, Location; SigninLogs | where TimeGenerated > ago(1d) | join kind=leftanti KnownCountries on UserPrincipalName, Location | project TimeGenerated, UserPrincipalName, Location, IPAddress, AppDisplayName, ResultType // Successful Sign-ins After Multiple Failures SigninLogs | where TimeGenerated > ago(1h) | summarize FailCount = countif(ResultType != 0), SuccessCount = countif(ResultType == 0), LastResult = arg_max(TimeGenerated, ResultType) by UserPrincipalName, IPAddress | where FailCount > 5 and SuccessCount > 0 | project UserPrincipalName, IPAddress, FailCount, SuccessCount // MFA Bypass Attempts (Legacy Auth) SigninLogs | where TimeGenerated > ago(24h) | where ClientAppUsed in ("Exchange ActiveSync", "IMAP4", "POP3", "SMTP", "Other clients") | summarize count() by UserPrincipalName, ClientAppUsed | order by count_ desc
8

Privileged Access Queries

KQL
// PIM Role Activations AuditLogs | where TimeGenerated > ago(7d) | where OperationName has "PIM" and OperationName has "activated" | extend User = tostring(InitiatedBy.user.userPrincipalName) | extend Role = tostring(TargetResources[0].displayName) | project TimeGenerated, User, Role, OperationName | order by TimeGenerated desc // Global Admin Role Assignments AuditLogs | where TimeGenerated > ago(30d) | where OperationName == "Add member to role" | extend Role = tostring(TargetResources[0].modifiedProperties[1].newValue) | where Role has "Global Administrator" or Role has "62e90394-69f5-4237-9190-012177145e10" | extend AssignedUser = tostring(TargetResources[0].userPrincipalName) | extend AssignedBy = tostring(InitiatedBy.user.userPrincipalName) | project TimeGenerated, AssignedUser, AssignedBy, Role // Privileged Role Members (Current) IdentityInfo | where TimeGenerated > ago(1d) | where AssignedRoles has "Global Administrator" or AssignedRoles has "Privileged Role Administrator" | project AccountUPN, AssignedRoles, Department, JobTitle // Service Principal Permission Changes AuditLogs | where TimeGenerated > ago(7d) | where OperationName in ( "Add app role assignment to service principal", "Add delegated permission grant", "Consent to application") | extend AppName = tostring(TargetResources[0].displayName) | extend Permission = tostring(TargetResources[0].modifiedProperties) | extend Actor = tostring(InitiatedBy.user.userPrincipalName) | project TimeGenerated, Actor, AppName, OperationName, Permission
9

Risk-Based Queries

KQL
// High-Risk Sign-ins SigninLogs | where TimeGenerated > ago(24h) | where RiskLevelDuringSignIn in ("high", "medium") | project TimeGenerated, UserPrincipalName, RiskLevelDuringSignIn, RiskEventTypes, IPAddress, Location, AppDisplayName | order by TimeGenerated desc // Users with Multiple Risk Detections AADUserRiskEvents | where TimeGenerated > ago(30d) | summarize RiskCount = count(), RiskTypes = make_set(RiskEventType), LastDetection = max(TimeGenerated) by UserPrincipalName | where RiskCount > 3 | order by RiskCount desc // Impossible Travel Detection SigninLogs | where TimeGenerated > ago(24h) | where ResultType == 0 | summarize Locations = make_set(Location), LocationCount = dcount(Location), SigninTimes = make_list(TimeGenerated) by UserPrincipalName, bin(TimeGenerated, 1h) | where LocationCount > 1 | project UserPrincipalName, Locations, SigninTimes // Anonymous IP Sign-ins SigninLogs | where TimeGenerated > ago(7d) | where RiskEventTypes has "anonymizedIPAddress" or IPAddress in ( // Known Tor exit nodes (example) "185.220.101.0/24" ) | project TimeGenerated, UserPrincipalName, IPAddress, Location
10

Lateral Movement & Reconnaissance

KQL
// User Enumeration Attempts SigninLogs | where TimeGenerated > ago(1h) | where ResultType == 50034 // User not found | summarize AttemptedUsers = dcount(UserPrincipalName), SourceIPs = make_set(IPAddress) by bin(TimeGenerated, 5m) | where AttemptedUsers > 10 // Password Spray Detection SigninLogs | where TimeGenerated > ago(1h) | where ResultType in (50126, 50053) // Invalid password, locked | summarize TargetedUsers = dcount(UserPrincipalName), Attempts = count() by IPAddress, bin(TimeGenerated, 5m) | where TargetedUsers > 10 | project TimeGenerated, IPAddress, TargetedUsers, Attempts // Group Membership Reconnaissance AuditLogs | where TimeGenerated > ago(24h) | where OperationName in ( "Get group members", "Get group", "List groups") | extend Actor = tostring(InitiatedBy.user.userPrincipalName) | summarize QueryCount = count() by Actor, bin(TimeGenerated, 1h) | where QueryCount > 50 // Mass Download of User List AuditLogs | where TimeGenerated > ago(24h) | where OperationName == "Download users" | extend Actor = tostring(InitiatedBy.user.userPrincipalName) | project TimeGenerated, Actor, OperationName
11

Conditional Access Analysis

KQL
// Conditional Access Failures SigninLogs | where TimeGenerated > ago(24h) | mv-expand ConditionalAccessPolicies | extend PolicyName = tostring(ConditionalAccessPolicies.displayName) | extend PolicyResult = tostring(ConditionalAccessPolicies.result) | where PolicyResult == "failure" | summarize count() by PolicyName, UserPrincipalName | order by count_ desc // Sign-ins Bypassing All Policies SigninLogs | where TimeGenerated > ago(24h) | where ResultType == 0 // Successful | mv-expand ConditionalAccessPolicies | extend PolicyResult = tostring(ConditionalAccessPolicies.result) | summarize AppliedPolicies = countif(PolicyResult == "success"), NotApplied = countif(PolicyResult == "notApplied") by UserPrincipalName, IPAddress | where AppliedPolicies == 0 and NotApplied > 0 | project UserPrincipalName, IPAddress, NotApplied // Conditional Access Policy Changes AuditLogs | where TimeGenerated > ago(7d) | where OperationName has "conditional access" | extend PolicyName = tostring(TargetResources[0].displayName) | extend Actor = tostring(InitiatedBy.user.userPrincipalName) | project TimeGenerated, Actor, OperationName, PolicyName

⚡ Module 3: Analytics Rules for Identity Threats

Module 3: Create Detection Rules for Identity Attacks

Build scheduled and near-real-time analytics rules.

⏱️ 60-90 minutes🎯 5 steps
12

Analytics Rule Types

Analytics
Analytics Rule Types: 1. SCHEDULED RULES - Run KQL query on schedule (5min - 14 days) - Most common type - Full customization 2. NRT (Near Real-Time) RULES - Run every minute - 30-second latency - For critical detections 3. FUSION RULES - ML-based correlation - Built-in, Microsoft-managed - Multi-stage attack detection 4. MICROSOFT SECURITY RULES - Import alerts from other products - Defender for Identity, Cloud Apps, etc. 5. ANOMALY RULES - ML-based anomaly detection - UEBA-powered - Customizable thresholds Navigate to: Sentinel → Analytics → Rule templates (pre-built) → Active rules (currently enabled) → Create → Scheduled query rule
13

Create: Brute Force Attack Detection

Scheduled Rule
// Navigate to: Analytics → Create → Scheduled query rule // GENERAL TAB Name: Identity - Brute Force Attack Detected Description: Detects multiple failed sign-ins followed by success Severity: High MITRE ATT&CK: Credential Access - Brute Force (T1110) Status: Enabled // SET RULE LOGIC TAB // Query: let threshold = 10; let timeframe = 1h; SigninLogs | where TimeGenerated > ago(timeframe) | summarize FailedCount = countif(ResultType != 0), SuccessCount = countif(ResultType == 0), IPAddresses = make_set(IPAddress), Apps = make_set(AppDisplayName), FailureCodes = make_set(ResultType) by UserPrincipalName | where FailedCount > threshold and SuccessCount > 0 | extend AccountName = tostring(split(UserPrincipalName, "@")[0]) | extend AccountDomain = tostring(split(UserPrincipalName, "@")[1]) | project UserPrincipalName, AccountName, AccountDomain, FailedCount, SuccessCount, IPAddresses, Apps // Entity Mapping: // - Account → UserPrincipalName (Name), AccountDomain (UPNSuffix) // - IP → IPAddresses // Query Scheduling: // - Run every: 1 hour // - Lookup data from: 1 hour // Alert Threshold: // - Generate alert when: Number of results > 0 // Event Grouping: // - Group events into single alert
14

Create: Suspicious PIM Activation

Scheduled Rule
// Rule: Global Admin PIM Activation Outside Business Hours // GENERAL TAB Name: Identity - Off-Hours Global Admin Activation Description: Global Admin role activated outside business hours Severity: High MITRE ATT&CK: Privilege Escalation - Valid Accounts (T1078) // QUERY: AuditLogs | where TimeGenerated > ago(1h) | where OperationName has "Add member to role in PIM completed" | extend Role = tostring(TargetResources[0].displayName) | where Role has "Global Administrator" | extend User = tostring(InitiatedBy.user.userPrincipalName) | extend Hour = hourofday(TimeGenerated) | extend DayOfWeek = dayofweek(TimeGenerated) // Outside 7 AM - 7 PM or weekends | where Hour < 7 or Hour > 19 or DayOfWeek in (0d, 6d) | project TimeGenerated, User, Role, Hour, DayOfWeek | extend AccountName = tostring(split(User, "@")[0]) | extend AccountDomain = tostring(split(User, "@")[1]) // Entity Mapping: // - Account → User // Scheduling: // - Run every: 1 hour // - Lookup: 1 hour // Incident Settings: // - Create incidents: Yes // - Group related alerts: Yes
15

Create: New Country Sign-in (NRT Rule)

NRT Rule
// Navigate to: Analytics → Create → NRT query rule // GENERAL TAB Name: Identity - Sign-in from New Country (NRT) Description: User signed in from country not seen in past 14 days Severity: Medium MITRE ATT&CK: Initial Access - Valid Accounts (T1078) // QUERY: let KnownLocations = SigninLogs | where TimeGenerated between (ago(14d) .. ago(1d)) | where ResultType == 0 | distinct UserPrincipalName, Location; SigninLogs | where TimeGenerated > ago(10m) // NRT queries recent data | where ResultType == 0 | join kind=leftanti KnownLocations on UserPrincipalName, Location | project TimeGenerated, UserPrincipalName, Location, IPAddress, AppDisplayName, DeviceDetail, UserAgent | extend AccountName = tostring(split(UserPrincipalName, "@")[0]) | extend AccountDomain = tostring(split(UserPrincipalName, "@")[1]) // Entity Mapping: // - Account → UserPrincipalName // - IP → IPAddress // - Host (if available) // NRT runs every minute automatically
16

Essential Identity Detection Rules

Rule Library
Password Spray Attack HIGH

Multiple users targeted from single IP with failed passwords

Impossible Travel HIGH

Sign-ins from geographically distant locations in short time

MFA Fatigue Attack HIGH

Multiple MFA prompts followed by approval (push bombing)

Service Principal Credential Added MEDIUM

New credentials added to service principal (persistence)

Admin Role Assigned Outside PIM HIGH

Direct role assignment bypassing PIM controls

Conditional Access Policy Modified MEDIUM

Changes to Conditional Access policies

Guest User Invited by Non-Admin LOW

Guest invitations from unexpected users

Mass User Creation MEDIUM

Large number of users created in short period

🧠 Module 4: User & Entity Behavior Analytics (UEBA)

Module 4: Enable and Use UEBA for Anomaly Detection

Leverage machine learning to detect behavioral anomalies.

⏱️ 45-60 minutes🎯 4 steps
17

Enable UEBA

UEBA
Navigate to: Sentinel → Settings → Settings tab → User and Entity Behavior Analytics ENABLE UEBA: ☑️ Enable UEBA DATA SOURCES: ☑️ Azure Active Directory (Entra ID) ☑️ Azure Activity ☑️ Security Events (Windows) ☑️ Office Activity ENTITY SYNC: ☑️ Sync Entra ID entities - Syncs users, groups, devices - Builds identity profiles → Apply UEBA Features Enabled: - Entity pages (user profiles) - Behavioral baselines - Anomaly scoring - Investigation priority - Timeline view Wait 24-48 hours for baseline to build
18

Explore Entity Pages

Entity Pages
Navigate to: Sentinel → Entity behavior Search for User: → Search "john@contoso.com" → Click user entity USER ENTITY PAGE SECTIONS: 1. OVERVIEW - Display name, UPN, job title - Department, manager - Risk score (if available) - Investigation priority 2. TIMELINE - All activities chronologically - Sign-ins, audit events - Anomalies highlighted 3. INSIGHTS - First time activities - Anomalous behaviors - Peer group comparison 4. ALERTS - Alerts involving this user - Related incidents 5. RELATED ENTITIES - Groups membership - Devices used - IPs connected from Investigation Priority Score: - 0-3: Low priority - 4-6: Medium priority - 7-10: High priority (investigate immediately)
19

UEBA Anomaly Detection

Anomalies
// Query UEBA Anomalies BehaviorAnalytics | where TimeGenerated > ago(24h) | where ActivityInsights has "True" // Anomaly detected | project TimeGenerated, UserPrincipalName, ActivityType, ActionType, ActivityInsights, InvestigationPriority, SourceIPAddress, SourceDevice | order by InvestigationPriority desc // High Priority Anomalies Only BehaviorAnalytics | where TimeGenerated > ago(7d) | where InvestigationPriority > 5 | summarize AnomalyCount = count(), Activities = make_set(ActivityType) by UserPrincipalName | order by AnomalyCount desc // First Time User Activity BehaviorAnalytics | where TimeGenerated > ago(24h) | where ActivityInsights has "FirstTimeUserUsedApp" or ActivityInsights has "FirstTimeUserConnectedFromCountry" | project TimeGenerated, UserPrincipalName, ActivityType, ActivityInsights // Anomalous Resource Access BehaviorAnalytics | where TimeGenerated > ago(7d) | where ActionType == "ResourceAccess" | where ActivityInsights has "AnomalousResource" | project TimeGenerated, UserPrincipalName, DestinationResource, ActivityInsights
20

Custom UEBA Analytics Rules

Analytics
// Analytics Rule: High Priority UEBA Anomaly // Create Scheduled Rule Name: UEBA - High Investigation Priority User Severity: Medium MITRE: Discovery - Account Discovery (T1087) // Query: BehaviorAnalytics | where TimeGenerated > ago(1h) | where InvestigationPriority >= 7 | summarize AnomalyTypes = make_set(ActivityInsights), Activities = make_set(ActivityType), Devices = make_set(SourceDevice), IPs = make_set(SourceIPAddress) by UserPrincipalName | extend AccountName = tostring(split(UserPrincipalName, "@")[0]) | extend AccountDomain = tostring(split(UserPrincipalName, "@")[1]) --- // Analytics Rule: User Accessing Unusual Resource Name: UEBA - Anomalous Resource Access Severity: Low // Query: BehaviorAnalytics | where TimeGenerated > ago(1h) | where ActivityInsights has "FirstTimeUserAccessedResource" or ActivityInsights has "ResourceUncommonlyAccessedByUser" | project TimeGenerated, UserPrincipalName, DestinationResource, ActivityInsights, InvestigationPriority | where InvestigationPriority > 3

🔎 Module 5: Incident Management & Investigation

Module 5: Investigate and Manage Security Incidents

Master the incident workflow and investigation tools.

⏱️ 45-60 minutes🎯 4 steps
21

Incident Queue Management

Incidents
Navigate to: Sentinel → Incidents INCIDENT QUEUE: - List of all incidents - Sortable by severity, status, time - Filterable by owner, product, etc. INCIDENT PROPERTIES: - Title: From analytics rule name - Severity: High/Medium/Low/Informational - Status: New → Active → Closed - Owner: Assigned analyst - Product: Source (Sentinel, Defender, etc.) TRIAGE WORKFLOW: 1. Review new incidents 2. Assess severity and validity 3. Assign owner 4. Set status to "Active" 5. Investigate 6. Respond/Remediate 7. Close with classification CLASSIFICATION OPTIONS: - True Positive - Suspicious activity - Benign Positive - Expected behavior - False Positive - Incorrect detection - Undetermined BULK OPERATIONS: - Select multiple incidents - Change status - Assign owner - Add tags
22

Incident Investigation

Investigation
Open Incident → Click "Full details" INVESTIGATION TAB: Evidence: - Alerts that created incident - Events from query results - Bookmarks added Entities: - Users involved - IP addresses - Hosts/devices - Applications Timeline: - Chronological view of events - Related activities - Entity actions Comments: - Add investigation notes - Collaboration with team - Audit trail --- INVESTIGATION GRAPH: → Click "Investigate" Visual representation: - Central entity (user/IP) - Related entities connected - Expand to see relationships - Click entity for details Entity Expansion: - User → Sign-ins, groups, devices - IP → Other users from same IP - Host → Users who logged in Useful for: - Understanding attack scope - Finding lateral movement - Identifying all affected entities
23

Investigation Queries

KQL
// Investigate Specific User (Last 24 Hours) let TargetUser = "compromised.user@contoso.com"; union SigninLogs, AuditLogs | where TimeGenerated > ago(24h) | where UserPrincipalName == TargetUser or InitiatedBy.user.userPrincipalName == TargetUser | project TimeGenerated, Type, OperationName, ResultType, IPAddress, Location | order by TimeGenerated desc // All Activity from Suspicious IP let SuspiciousIP = "203.0.113.50"; SigninLogs | where TimeGenerated > ago(7d) | where IPAddress == SuspiciousIP | project TimeGenerated, UserPrincipalName, AppDisplayName, ResultType, Location // User's Device History SigninLogs | where TimeGenerated > ago(30d) | where UserPrincipalName == "user@contoso.com" | extend DeviceName = tostring(DeviceDetail.displayName) | extend DeviceOS = tostring(DeviceDetail.operatingSystem) | distinct DeviceName, DeviceOS, DeviceDetail // Actions Taken by Compromised Account AuditLogs | where TimeGenerated > ago(7d) | where InitiatedBy.user.userPrincipalName == "compromised@contoso.com" | project TimeGenerated, OperationName, TargetResources, Result | order by TimeGenerated desc
24

Incident Response Checklist

Response
Identity Incident Response Checklist: IMMEDIATE ACTIONS (Containment): ☐ Disable compromised account ☐ Revoke all sessions (Entra ID) ☐ Reset password ☐ Revoke refresh tokens ☐ Block suspicious IP (Conditional Access) INVESTIGATION: ☐ Determine scope (how many accounts?) ☐ Identify initial access vector ☐ Check for persistence mechanisms ☐ Review privileged access changes ☐ Check for data exfiltration ☐ Identify affected applications PERSISTENCE CHECK: ☐ New MFA devices registered? ☐ New OAuth applications consented? ☐ New credentials on service principals? ☐ New mail forwarding rules? ☐ New admin role assignments? ERADICATION: ☐ Remove malicious OAuth apps ☐ Remove unauthorized MFA devices ☐ Remove mail forwarding rules ☐ Remove added credentials ☐ Revert privilege escalations RECOVERY: ☐ Re-enable account with new password ☐ Re-register MFA ☐ Educate user on phishing ☐ Monitor for re-compromise POST-INCIDENT: ☐ Document timeline ☐ Update detection rules ☐ Lessons learned ☐ Close incident with classification

🤖 Module 6: Automated Response Playbooks

Module 6: Create SOAR Playbooks for Identity Incidents

Automate incident response with Logic Apps.

⏱️ 60-90 minutes🎯 5 steps
25

Playbook Architecture

Playbooks
Sentinel Playbooks = Azure Logic Apps Components: 1. TRIGGER - When incident is created - When alert is triggered - Manual trigger 2. CONNECTOR ACTIONS - Entra ID (disable user, revoke sessions) - Microsoft Teams (post message) - Office 365 (send email) - Microsoft Graph (API calls) - ServiceNow (create ticket) 3. LOGIC - Conditions (if/then) - Loops (for each entity) - Variables Prerequisites: - Logic App Contributor role - Appropriate permissions on target systems - Managed Identity or service principal Navigate to: Sentinel → Automation → Playbook templates (pre-built) → Active playbooks (configured) → Create → Playbook (Logic App)
26

Playbook: Disable Compromised User

Logic App
Create Playbook: Disable-CompromisedUser 1. CREATE LOGIC APP: Navigate to: Sentinel → Automation → Create → Playbook - Name: Disable-CompromisedUser - Resource group: rg-security - Enable managed identity: Yes 2. TRIGGER: - Microsoft Sentinel incident (When incident is created) - Or: Microsoft Sentinel alert 3. GET INCIDENT ENTITIES: - Action: Entities - Get Accounts - Incident ARM ID: From trigger 4. FOR EACH ACCOUNT: - Loop through accounts 5. DISABLE USER (Azure AD Connector): - Action: Update user - User ID: Account object ID - Account Enabled: false 6. REVOKE SESSIONS (HTTP Action): POST https://graph.microsoft.com/v1.0/users/{id}/revokeSignInSessions - Authentication: Managed Identity - Audience: https://graph.microsoft.com 7. POST TO TEAMS: - Action: Post message (Teams) - Channel: Security-Alerts - Message: "User [UPN] disabled due to incident [ID]" 8. ADD COMMENT TO INCIDENT: - Action: Add comment to incident - Comment: "Automated response: User disabled, sessions revoked" PERMISSIONS NEEDED: - User.ReadWrite.All (disable user) - User.RevokeSessions.All (revoke sessions)
27

Playbook: Block IP in Conditional Access

Logic App
// Playbook: Block-MaliciousIP // This playbook adds IP to a Named Location (blocklist) // which is used in Conditional Access to block sign-ins // PRE-REQUISITES: // 1. Create Named Location in Entra ID: // - Name: "Blocked-IPs-Sentinel" // - Type: IP ranges // - Initially empty // 2. Create CA Policy: // - Condition: Location = "Blocked-IPs-Sentinel" // - Access: Block // LOGIC APP FLOW: // 1. Trigger: Microsoft Sentinel incident // 2. Get IPs: // Action: Entities - Get IPs // Input: Incident ARM ID // 3. For Each IP: // 4. Get Current Named Location (HTTP): GET https://graph.microsoft.com/v1.0/identity/conditionalAccess/namedLocations/{id} // 5. Add IP to list: // Parse current IPs, append new IP // 6. Update Named Location (HTTP): PATCH https://graph.microsoft.com/v1.0/identity/conditionalAccess/namedLocations/{id} { "@odata.type": "#microsoft.graph.ipNamedLocation", "ipRanges": [ {"@odata.type": "#microsoft.graph.iPv4CidrRange", "cidrAddress": "x.x.x.x/32"} ] } // 7. Add incident comment: // "IP x.x.x.x added to block list"
28

Playbook: Enrich Incident with User Details

Logic App
Playbook: Enrich-UserDetails Purpose: Add user context to incident for faster triage FLOW: 1. Trigger: When incident created 2. Get account entities from incident 3. For each account: 4. Get User Details (Graph API): GET /users/{id}?$select=displayName,jobTitle, department,manager,accountEnabled 5. Get User Risk (Graph API): GET /identityProtection/riskyUsers/{id} 6. Get Recent Sign-ins (Graph API): GET /auditLogs/signIns?$filter=userId eq '{id}' &$top=5&$orderby=createdDateTime desc 7. Get Group Memberships (Graph API): GET /users/{id}/memberOf 8. Build enrichment summary: - Name: John Doe - Title: Finance Manager - Department: Finance - Manager: Jane Smith - Risk Level: High - Recent Sign-ins: 5 from US, 1 from Russia (!) - Privileged Groups: None 9. Add comment to incident with enrichment 10. Update incident description with context Benefits: - Analyst has context immediately - Faster triage decisions - Consistent enrichment process
29

Attach Playbooks to Analytics Rules

Automation
Attach Playbook to Rule: Option 1: Via Analytics Rule → Analytics → Select rule → Edit → Automated response tab → Add playbook → Select playbook (must have incident trigger) Option 2: Via Automation Rules → Automation → Create → Automation rule Automation Rule Configuration: - Name: Auto-respond to brute force - Trigger: When incident is created - Conditions: - Analytic rule name Contains "Brute Force" - Severity Equals High - Actions: - Run playbook: Disable-CompromisedUser - Change status: Active - Assign owner: SOC Team Automation Rule Benefits: - Apply playbooks to multiple rules - Conditional execution - Change incident properties - Route to specific teams

📈 Module 7: Workbooks & Dashboards

Module 7: Build Identity Security Dashboards

Create visual dashboards for identity monitoring.

⏱️ 45-60 minutes🎯 4 steps
30

Built-in Identity Workbooks

Workbooks
Navigate to: Sentinel → Workbooks → Templates IDENTITY WORKBOOKS: 1. Azure AD Sign-in Logs - Sign-in trends - Failure analysis - MFA statistics - Conditional Access impact 2. Azure AD Audit Logs - Directory changes - Role assignments - Application changes 3. Insecure Protocols - Legacy authentication usage - Users at risk - Apps using legacy auth 4. Identity and Access - Privileged users overview - Risky sign-ins - Risk detections 5. User and Entity Behavior Analytics - Anomaly trends - Top risky users - Investigation priorities INSTALL WORKBOOK: → Select template → Save (creates copy in your workspace) → View saved workbooks
31

Create Custom Identity Dashboard

Workbook
// Navigate to: Workbooks → Add workbook → Edit // Add Parameter: Time Range // Type: Time range picker // Default: Last 24 hours // ========== TILE 1: Sign-in Summary ========== // Add Query → Paste: SigninLogs | where TimeGenerated {TimeRange} | summarize TotalSignins = count(), SuccessfulSignins = countif(ResultType == 0), FailedSignins = countif(ResultType != 0), UniqueUsers = dcount(UserPrincipalName) | project TotalSignins, SuccessfulSignins, FailedSignins, UniqueUsers // Visualization: Tiles // ========== TILE 2: Failed Sign-ins Over Time ========== SigninLogs | where TimeGenerated {TimeRange} | where ResultType != 0 | summarize FailedCount = count() by bin(TimeGenerated, 1h) | render timechart // ========== TILE 3: Top Failed Users ========== SigninLogs | where TimeGenerated {TimeRange} | where ResultType != 0 | summarize FailCount = count() by UserPrincipalName | top 10 by FailCount | render barchart // ========== TILE 4: Risky Sign-ins ========== SigninLogs | where TimeGenerated {TimeRange} | where RiskLevelDuringSignIn in ("high", "medium") | summarize count() by RiskLevelDuringSignIn | render piechart // ========== TILE 5: Sign-ins by Location ========== SigninLogs | where TimeGenerated {TimeRange} | where ResultType == 0 | summarize count() by Location | top 10 by count_ | render piechart
32

PIM Activity Dashboard

Workbook
// PIM Monitoring Dashboard // ========== PIM Activations Over Time ========== AuditLogs | where TimeGenerated {TimeRange} | where OperationName has "PIM" and OperationName has "activated" | summarize Activations = count() by bin(TimeGenerated, 4h) | render timechart // ========== Activations by Role ========== AuditLogs | where TimeGenerated {TimeRange} | where OperationName has "PIM" and OperationName has "activated" | extend Role = tostring(TargetResources[0].displayName) | summarize count() by Role | render piechart // ========== Top Users Activating Roles ========== AuditLogs | where TimeGenerated {TimeRange} | where OperationName has "PIM" and OperationName has "activated" | extend User = tostring(InitiatedBy.user.userPrincipalName) | summarize ActivationCount = count() by User | top 10 by ActivationCount | render barchart // ========== Off-Hours Activations ========== AuditLogs | where TimeGenerated {TimeRange} | where OperationName has "PIM" and OperationName has "activated" | extend Hour = hourofday(TimeGenerated) | extend OffHours = iff(Hour < 7 or Hour > 19, "Off-Hours", "Business Hours") | summarize count() by OffHours | render piechart // ========== Recent Activations Table ========== AuditLogs | where TimeGenerated {TimeRange} | where OperationName has "PIM" and OperationName has "activated" | extend User = tostring(InitiatedBy.user.userPrincipalName) | extend Role = tostring(TargetResources[0].displayName) | project TimeGenerated, User, Role, OperationName | order by TimeGenerated desc | take 50
33

Export and Share Dashboards

Sharing
Sharing Workbooks: SAVE OPTIONS: → Save → Save as - Save to: My Workbooks (personal) or Shared (team) - Name: Identity Security Dashboard - Subscription/Resource Group: Select SHARE WITH TEAM: - Shared workbooks visible to all with Reader access - Can pin to Azure Dashboard EXPORT OPTIONS: → More → Export - Download as .workbook file - Import to other workspaces PIN TO AZURE DASHBOARD: - Individual tiles can be pinned - Creates quick-access dashboard SCHEDULED REPORTS: Currently requires: 1. Logic App with recurrence trigger 2. Run KQL query via Log Analytics API 3. Format results 4. Email via Office 365 connector Or use: - Power BI with Log Analytics connector - Scheduled refresh and email subscriptions

🎯 Module 8: Threat Hunting for Identity Attacks

Module 8: Proactive Identity Threat Hunting

Hunt for advanced identity-based threats.

⏱️ 60-90 minutes🎯 5 steps
34

Hunting Queries Overview

Hunting
Navigate to: Sentinel → Hunting HUNTING FEATURES: - Pre-built queries (templates) - Custom queries - Bookmarks (save findings) - Livestream (real-time) HUNTING WORKFLOW: 1. Form hypothesis 2. Write/select query 3. Run query 4. Analyze results 5. Bookmark interesting findings 6. Create incident if threat confirmed HYPOTHESIS EXAMPLES: - "Attackers may have added persistence via OAuth apps" - "Compromised accounts may be accessing unusual resources" - "Lateral movement via PIM abuse" BUILT-IN IDENTITY HUNTING QUERIES: → Hunting → Queries → Filter by "Identity" Categories: - Credential Access - Persistence - Privilege Escalation - Initial Access - Discovery
35

Hunt: OAuth Application Abuse

Hunting
// Hunt for malicious OAuth app persistence // New OAuth Apps with Mail.Read Permission AuditLogs | where TimeGenerated > ago(30d) | where OperationName == "Consent to application" | extend AppName = tostring(TargetResources[0].displayName) | extend Permissions = tostring(TargetResources[0].modifiedProperties) | where Permissions has "Mail.Read" or Permissions has "Mail.ReadWrite" | extend ConsentedBy = tostring(InitiatedBy.user.userPrincipalName) | project TimeGenerated, ConsentedBy, AppName, Permissions // Apps with High-Risk Permissions AuditLogs | where TimeGenerated > ago(30d) | where OperationName in ( "Add delegated permission grant", "Add app role assignment to service principal") | extend Permissions = tostring(TargetResources[0].modifiedProperties) | where Permissions has_any ( "Directory.ReadWrite.All", "Mail.ReadWrite", "Files.ReadWrite.All", "RoleManagement.ReadWrite.Directory") | extend AppName = tostring(TargetResources[0].displayName) | extend Actor = tostring(InitiatedBy.user.userPrincipalName) | project TimeGenerated, Actor, AppName, Permissions // Service Principal Credentials Added AuditLogs | where TimeGenerated > ago(30d) | where OperationName in ( "Add service principal credentials", "Update application – Certificates and secrets management") | extend AppName = tostring(TargetResources[0].displayName) | extend Actor = tostring(InitiatedBy.user.userPrincipalName) | project TimeGenerated, Actor, AppName, OperationName
36

Hunt: Privilege Escalation Patterns

Hunting
// Hunt for privilege escalation attempts // Users Added to Privileged Groups AuditLogs | where TimeGenerated > ago(7d) | where OperationName == "Add member to group" | extend GroupName = tostring(TargetResources[0].displayName) | where GroupName has_any ( "Global Administrator", "Privileged", "Admin", "Domain Admins", "Enterprise Admins") | extend AddedUser = tostring(TargetResources[1].userPrincipalName) | extend AddedBy = tostring(InitiatedBy.user.userPrincipalName) | project TimeGenerated, AddedBy, AddedUser, GroupName // Direct Role Assignments (Bypassing PIM) AuditLogs | where TimeGenerated > ago(7d) | where OperationName == "Add member to role" | where OperationName !has "PIM" // Not via PIM | extend Role = tostring(TargetResources[0].displayName) | extend AssignedUser = tostring(TargetResources[0].userPrincipalName) | extend AssignedBy = tostring(InitiatedBy.user.userPrincipalName) | project TimeGenerated, AssignedBy, AssignedUser, Role // Password Reset of Privileged User by Non-Admin let PrivilegedUsers = IdentityInfo | where AssignedRoles has_any ("Global", "Privileged", "Admin") | distinct AccountUPN; AuditLogs | where TimeGenerated > ago(7d) | where OperationName in ("Reset password", "Reset user password") | extend ResetUser = tostring(TargetResources[0].userPrincipalName) | extend ResetBy = tostring(InitiatedBy.user.userPrincipalName) | where ResetUser in (PrivilegedUsers) | where ResetBy !in (PrivilegedUsers) // Non-admin resetting admin | project TimeGenerated, ResetBy, ResetUser, OperationName
37

Hunt: Suspicious Sign-in Patterns

Hunting
// Token Theft / Session Hijacking Indicators // Same User, Multiple IPs in Short Window SigninLogs | where TimeGenerated > ago(24h) | where ResultType == 0 | summarize IPCount = dcount(IPAddress), IPs = make_set(IPAddress), Locations = make_set(Location) by UserPrincipalName, bin(TimeGenerated, 15m) | where IPCount > 3 | project TimeGenerated, UserPrincipalName, IPCount, IPs, Locations // Sign-ins Without MFA After Successful MFA SigninLogs | where TimeGenerated > ago(24h) | where ResultType == 0 | extend MFAUsed = tostring(AuthenticationRequirement) | summarize MFASessions = countif(MFAUsed == "multiFactorAuthentication"), NoMFASessions = countif(MFAUsed == "singleFactorAuthentication") by UserPrincipalName | where MFASessions > 0 and NoMFASessions > 0 // Suspicious User Agent Strings SigninLogs | where TimeGenerated > ago(7d) | where ResultType == 0 | extend UA = tostring(DeviceDetail.browser) | where UA has_any ("curl", "python", "wget", "powershell", "httpie") | project TimeGenerated, UserPrincipalName, UA, IPAddress, AppDisplayName // Sign-ins to Sensitive Apps from New Device let SensitiveApps = dynamic(["Azure Portal", "Microsoft Azure", "Office 365 Exchange Online", "Microsoft Graph"]); let KnownDevices = SigninLogs | where TimeGenerated between(ago(30d) .. ago(1d)) | where ResultType == 0 | distinct UserPrincipalName, DeviceDetail; SigninLogs | where TimeGenerated > ago(1d) | where ResultType == 0 | where AppDisplayName in (SensitiveApps) | join kind=leftanti KnownDevices on UserPrincipalName, DeviceDetail | project TimeGenerated, UserPrincipalName, AppDisplayName, DeviceDetail
38

Save Hunting Results as Bookmarks

Bookmarks
Using Bookmarks: When hunting query finds suspicious activity: 1. SELECT ROWS: - Check boxes next to suspicious entries 2. ADD BOOKMARK: → Click "Add bookmark" - Name: "Suspicious OAuth app - Finance user" - Notes: Add investigation notes - Tags: "OAuth", "Persistence" - Entity mapping: Map user, IP, etc. 3. VIEW BOOKMARKS: → Hunting → Bookmarks - All saved findings - Can filter/search 4. CREATE INCIDENT FROM BOOKMARKS: → Select bookmarks → "Create incident" - Combines bookmarks into incident - Entities automatically mapped 5. SHARE WITH TEAM: - Bookmarks visible to team - Add to investigation notes Benefits: - Don't lose hunting findings - Build case over time - Easy incident creation - Collaboration on investigations

🎯 Identity Security Use Cases

🔺 MITRE ATT&CK Coverage - Identity

T1078 - Valid Accounts T1110 - Brute Force T1556 - Modify Authentication T1098 - Account Manipulation T1136 - Create Account T1087 - Account Discovery T1550 - Use Alternate Auth T1528 - Steal App Access Token T1606 - Forge Web Credentials T1621 - MFA Request Generation
Critical Detections

Password spray, Impossible travel, Admin role abuse, Token theft

High Priority

New country sign-in, OAuth app consent, MFA bypass

Medium Priority

Legacy auth usage, Failed MFA, Unusual app access

Informational

New device, Group changes, Policy modifications

✅ Sentinel Identity Security Best Practices

  • Enable all identity connectors: Entra ID, Identity Protection, Defender for Identity
  • Enable UEBA: Behavioral baselines catch what rules miss
  • Tune analytics rules: Reduce false positives, adjust thresholds
  • Create playbooks: Automate response for high-confidence detections
  • Regular hunting: Proactively search for threats weekly
  • Maintain workbooks: SOC dashboards for identity monitoring
  • Integrate with ticketing: ServiceNow, Jira for incident tracking
  • Review closed incidents: Improve detections from false positives
  • Correlate with other sources: Endpoint, network, cloud for full picture
  • Document runbooks: Standard procedures for identity incidents