# KQL Query Pack — DFIR Assist
# Generated: 2026-03-07
# Total queries: 105

queries:
  - node: "Timeframe Bounding"
    stage: "triage"
    query: |
      index=* (sourcetype=crowdstrike OR sourcetype=defender OR sourcetype=sysmon) earliest=-30d | stats earliest(_time) as first_seen latest(_time) as last_seen by src_ip, dest_ip, process_name | where first_seen < relative_time(now(), "-7d")

  - node: "Timeframe Bounding"
    stage: "triage"
    query: |
      SecurityEvent | where TimeGenerated between (ago(30d) .. now()) | where EventID in (4624, 4625, 4648, 4672) | summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated) by Account, Computer, LogonType | order by FirstSeen asc

  - node: "Timeframe Bounding"
    stage: "triage"
    query: |
      DeviceProcessEvents | where Timestamp > ago(30d) | where FileName in~ ("powershell.exe","cmd.exe","wscript.exe","mshta.exe","certutil.exe") | summarize EarliestExec=min(Timestamp), Count=count() by DeviceName, FileName, InitiatingProcessFileName | order by EarliestExec asc

  - node: "Patient Zero"
    stage: "triage"
    query: |
      DeviceLogonEvents | where Timestamp between (datetime(T_START) .. datetime(T_END)) | where LogonType in ("RemoteInteractive", "Network", "NewCredentials") | summarize FirstLogon=min(Timestamp), LogonCount=count() by DeviceName, AccountName, RemoteIP, LogonType | order by FirstLogon asc | take 50

  - node: "Patient Zero"
    stage: "triage"
    query: |
      EmailEvents | where Timestamp > ago(30d) | where DeliveryAction == "Delivered" | join kind=inner (EmailAttachmentInfo | where FileType in ("exe","dll","js","vbs","hta","iso","img")) on NetworkMessageId | summarize Earliest=min(Timestamp) by SenderFromAddress, RecipientEmailAddress, Subject, FileName | order by Earliest asc

  - node: "Access Validation"
    stage: "triage"
    query: |
      SigninLogs | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where UserPrincipalName == "compromised_user@domain.com" | where ResultType == 0 | project TimeGenerated, IPAddress, Location, ClientAppUsed, DeviceDetail, ConditionalAccessStatus | order by TimeGenerated asc

  - node: "Access Validation"
    stage: "triage"
    query: |
      DeviceProcessEvents | where DeviceName == "PATIENT_ZERO" | where Timestamp between (datetime(T_START) .. datetime(T_END)) | where InitiatingProcessFileName in~ ("outlook.exe","winword.exe","excel.exe","powerpnt.exe","w3wp.exe","httpd","nginx") | project Timestamp, FileName, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine | order by Timestamp asc

  - node: "Network Isolation"
    stage: "contain"
    query: |
      DeviceNetworkEvents | where DeviceName == "COMPROMISED_HOST" | where Timestamp > ago(1h) | summarize ConnectionCount=count() by RemoteIP, RemotePort, ActionType | order by ConnectionCount desc

  - node: "Account Lockdown"
    stage: "contain"
    query: |
      let compromised_user = "user@domain.com"; SigninLogs | where UserPrincipalName == compromised_user | where TimeGenerated > ago(7d) | where ResultType == 0 | summarize by IPAddress, AppDisplayName, ClientAppUsed, DeviceDetail_string=tostring(DeviceDetail) | order by IPAddress

  - node: "Account Lockdown"
    stage: "contain"
    query: |
      let compromised_user = "user@domain.com"; AuditLogs | where InitiatedBy has compromised_user | where TimeGenerated > ago(30d) | where Category == "ApplicationManagement" | project TimeGenerated, ActivityDisplayName, TargetResources | order by TimeGenerated desc

  - node: "Account Lockdown"
    stage: "contain"
    query: |
      CloudAppEvents | where AccountObjectId == "<user_object_id>" | where Timestamp > ago(7d) | where ActionType in ("MailItemsAccessed", "FileDownloaded", "FileUploaded", "Set-InboxRule") | summarize Count=count() by ActionType, bin(Timestamp, 1h) | order by Timestamp desc

  - node: "Memory Capture"
    stage: "preserve"
    query: |
      DeviceProcessEvents | where DeviceName == "TARGET_HOST" | where Timestamp > ago(1h) | where FileName in~ ("winpmem","ramcapture","avml","lime") | project Timestamp, FileName, ProcessCommandLine, AccountName

  - node: "Log Snapshot"
    stage: "preserve"
    query: |
      index=_internal sourcetype=splunkd component=HotBucketRoller OR component=WarmToColdManager | stats latest(data_size) as current_size latest(max_size) as max_allowed by index | eval pct_used=round(current_size/max_allowed*100,1) | where pct_used > 80

  - node: "Log Snapshot"
    stage: "preserve"
    query: |
      AzureActivity | summarize LogCount=count(), EarliestLog=min(TimeGenerated), LatestLog=max(TimeGenerated) by SourceSystem, CategoryValue | order by EarliestLog asc

  - node: "EDR Collection"
    stage: "collect"
    query: |
      DeviceProcessEvents | where DeviceName in ("HOST1","HOST2","HOST3") | where Timestamp between (datetime(T_START) .. datetime(T_END)) | project Timestamp, DeviceName, ActionType, FileName, FolderPath, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine, AccountName, SHA256 | order by DeviceName, Timestamp asc

  - node: "EDR Collection"
    stage: "collect"
    query: |
      DeviceFileEvents | where DeviceName in ("HOST1","HOST2","HOST3") | where Timestamp between (datetime(T_START) .. datetime(T_END)) | where ActionType in ("FileCreated","FileModified","FileRenamed") | where FolderPath matches regex @"(?i)(\\temp\\|\\appdata\\|\\programdata\\|\\public\\)" | project Timestamp, DeviceName, ActionType, FileName, FolderPath, SHA256, InitiatingProcessFileName | order by Timestamp asc

  - node: "EDR Collection"
    stage: "collect"
    query: |
      DeviceRegistryEvents | where DeviceName in ("HOST1","HOST2","HOST3") | where Timestamp between (datetime(T_START) .. datetime(T_END)) | where RegistryKey has_any ("Run","RunOnce","Services","Wow6432Node\\Run","Image File Execution Options","AppInit_DLLs") | project Timestamp, DeviceName, ActionType, RegistryKey, RegistryValueName, RegistryValueData, InitiatingProcessFileName | order by Timestamp asc

  - node: "M365 UAL Collection"
    stage: "collect"
    query: |
      CloudAppEvents | where Timestamp between (datetime(T_START) .. datetime(T_END)) | where AccountObjectId == "<user_object_id>" | where ActionType in ("MailItemsAccessed","Send","SearchQueryInitiatedExchange","SearchQueryInitiatedSharePoint","FileDownloaded","FileUploaded","Set-Mailbox","New-InboxRule","Set-InboxRule","Add-MailboxPermission") | project Timestamp, ActionType, AccountDisplayName, IPAddress, RawEventData | order by Timestamp asc

  - node: "M365 UAL Collection"
    stage: "collect"
    query: |
      AuditLogs | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where Category in ("ApplicationManagement","UserManagement","Policy","RoleManagement") | where Result == "success" | project TimeGenerated, OperationName, InitiatedBy, TargetResources, AdditionalDetails | order by TimeGenerated desc

  - node: "M365 UAL Collection"
    stage: "collect"
    query: |
      OfficeActivity | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where Operation in ("New-InboxRule","Set-InboxRule","UpdateInboxRules","Add-MailboxPermission","AddFolderPermissions","Set-OwaMailboxPolicy","New-ManagementRoleAssignment") | project TimeGenerated, UserId, Operation, Parameters, ClientIP | order by TimeGenerated asc

  - node: "Lateral Movement"
    stage: "analyze"
    query: |
      SecurityEvent | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where EventID in (4624, 4648) | where LogonType in (3, 9, 10) | where Account !endswith "$" | where IpAddress != "-" and IpAddress != "::1" and IpAddress != "127.0.0.1" | summarize LogonCount=count(), DistinctSources=dcount(IpAddress), Hosts=make_set(Computer) by Account, LogonType | where DistinctSources > 2 | order by DistinctSources desc

  - node: "Lateral Movement"
    stage: "analyze"
    query: |
      DeviceProcessEvents | where Timestamp between (datetime(T_START) .. datetime(T_END)) | where FileName in~ ("psexec.exe","psexesvc.exe","wmic.exe","winrs.exe","mstsc.exe") or (FileName == "services.exe" and ProcessCommandLine has "psexesvc") or (InitiatingProcessFileName == "wmiprvse.exe" and FileName in~ ("powershell.exe","cmd.exe")) | project Timestamp, DeviceName, FileName, ProcessCommandLine, AccountName, InitiatingProcessFileName | order by Timestamp asc

  - node: "Lateral Movement"
    stage: "analyze"
    query: |
      let LateralHops = SecurityEvent | where EventID == 4624 and LogonType == 3 | where Account == "COMPROMISED_ACCOUNT" | project TimeGenerated, SourceHost=IpAddress, DestHost=Computer; let ServiceInstalls = SecurityEvent | where EventID == 7045 | project TimeGenerated, Computer, ServiceName, ServiceFileName; LateralHops | join kind=inner (ServiceInstalls) on $left.DestHost == $right.Computer | where TimeGenerated1 between (TimeGenerated .. datetime_add("minute", 5, TimeGenerated)) | project HopTime=TimeGenerated, SourceHost, DestHost, ServiceName, ServiceFileName

  - node: "Data Staging"
    stage: "analyze"
    query: |
      DeviceProcessEvents | where Timestamp between (datetime(T_START) .. datetime(T_END)) | where ProcessCommandLine has_any ("7z","rar","zip","tar","Compress-Archive","makecab") | where ProcessCommandLine has_any ("-p","-mx","-v","password","split") | project Timestamp, DeviceName, FileName, ProcessCommandLine, AccountName | order by Timestamp asc

  - node: "Data Staging"
    stage: "analyze"
    query: |
      DeviceFileEvents | where Timestamp between (datetime(T_START) .. datetime(T_END)) | where ActionType == "FileCreated" | where FileName endswith_cs ".7z" or FileName endswith_cs ".zip" or FileName endswith_cs ".rar" or FileName endswith_cs ".tar.gz" or FileName endswith_cs ".cab" | summarize TotalSizeMB=sum(FileSize)/1048576, FileCount=count() by DeviceName, FolderPath, bin(Timestamp, 1h) | where TotalSizeMB > 100 | order by TotalSizeMB desc

  - node: "Exfil Channels"
    stage: "analyze"
    query: |
      CommonSecurityLog | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where DeviceAction == "allowed" | where SentBytes > 10000000 | summarize TotalBytesSent=sum(SentBytes), ConnectionCount=count() by SourceIP, DestinationIP, DestinationHostName, ApplicationProtocol | extend TotalMB=TotalBytesSent/1048576 | order by TotalMB desc | take 50

  - node: "Exfil Channels"
    stage: "analyze"
    query: |
      DnsEvents | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | extend SubdomainLength=strlen(tostring(split(Name, ".")[0])) | where SubdomainLength > 30 | summarize QueryCount=count(), AvgSubdomainLen=avg(SubdomainLength) by ClientIP, Name | where QueryCount > 100 | order by QueryCount desc

  - node: "Block Exfiltration"
    stage: "contain"
    query: |
      CommonSecurityLog | where TimeGenerated > ago(1h) | where DestinationIP in ("EXFIL_IP1","EXFIL_IP2") or DestinationHostName in ("exfil-domain.com") | summarize BlockedCount=count() by DeviceAction, SourceIP, DestinationIP | where DeviceAction == "blocked"

  - node: "Block Exfiltration"
    stage: "contain"
    query: |
      DnsEvents | where TimeGenerated > ago(1h) | where Name has "exfil-domain.com" | summarize QueryCount=count() by ClientIP, Name, QueryType | order by QueryCount desc

  - node: "DLP Alerts"
    stage: "collect"
    query: |
      CloudAppEvents | where Timestamp between (datetime(T_START) .. datetime(T_END)) | where ActionType has "DLP" | project Timestamp, AccountDisplayName, ActionType, IPAddress, RawEventData | order by Timestamp asc

  - node: "DLP Alerts"
    stage: "collect"
    query: |
      OfficeActivity | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where Operation has "DLP" | project TimeGenerated, UserId, Operation, PolicyId, ConditionName, SensitiveInfoType | order by TimeGenerated asc

  - node: "Ransom Note Analysis"
    stage: "triage"
    query: |
      DeviceFileEvents | where Timestamp > ago(24h) | where ActionType == "FileRenamed" | where FileName endswith ".locked" or FileName endswith ".encrypted" or FileName endswith ".crypt" or FileName endswith ".enc" | summarize AffectedFiles=count(), EarliestRename=min(Timestamp) by DeviceName | order by EarliestRename asc

  - node: "Ransom Note Analysis"
    stage: "triage"
    query: |
      DeviceProcessEvents | where Timestamp > ago(24h) | where ProcessCommandLine has_any ("vssadmin delete shadows","wmic shadowcopy delete","bcdedit /set","wbadmin delete catalog") | project Timestamp, DeviceName, FileName, ProcessCommandLine, AccountName | order by Timestamp asc

  - node: "Stop Ransomware Spread"
    stage: "contain"
    query: |
      DeviceFileEvents | where Timestamp > ago(1h) | where ActionType == "FileRenamed" | summarize RenameCount=count() by DeviceName, bin(Timestamp, 5m) | where RenameCount > 100 | order by Timestamp desc

  - node: "Stop Ransomware Spread"
    stage: "contain"
    query: |
      DeviceNetworkEvents | where Timestamp > ago(1h) | where RemotePort == 445 | where ActionType == "ConnectionSuccess" | summarize SMBConnections=count(), TargetHosts=dcount(RemoteIP) by DeviceName | where TargetHosts > 5 | order by TargetHosts desc

  - node: "Encryption Scope"
    stage: "analyze"
    query: |
      DeviceFileEvents | where Timestamp > ago(48h) | where ActionType == "FileRenamed" | extend NewExtension=tostring(split(FileName, ".")[-1]) | where NewExtension in ("locked","encrypted","crypt","enc","ransom") | summarize EncryptedFiles=count(), FirstEncryption=min(Timestamp), LastEncryption=max(Timestamp) by DeviceName | order by FirstEncryption asc

  - node: "Encryption Scope"
    stage: "analyze"
    query: |
      DeviceEvents | where Timestamp > ago(48h) | where ActionType has "AntivirusDetection" | where AdditionalFields has "Ransom" | summarize DetectionCount=count() by DeviceName, AdditionalFields | order by DetectionCount desc

  - node: "Decryption Assessment"
    stage: "recover"
    query: |
      DeviceProcessEvents | where DeviceName == "ENCRYPTED_HOST" | where Timestamp > ago(48h) | where ProcessCommandLine has_any ("vssadmin","wmic shadowcopy","bcdedit","wbadmin") | project Timestamp, ProcessCommandLine, AccountName, InitiatingProcessFileName | order by Timestamp asc

  - node: "Azure AD Logs"
    stage: "collect"
    query: |
      SigninLogs | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where ResultType == 0 | summarize SignInCount=count(), DistinctIPs=dcount(IPAddress), DistinctApps=dcount(AppDisplayName) by UserPrincipalName, UserType | where DistinctIPs > 3 | order by DistinctIPs desc

  - node: "Azure AD Logs"
    stage: "collect"
    query: |
      AuditLogs | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where OperationName in ("Add member to role","Add owner to application","Add app role assignment to service principal","Consent to application","Add delegated permission grant") | project TimeGenerated, OperationName, InitiatedBy, TargetResources | order by TimeGenerated asc

  - node: "Azure AD Logs"
    stage: "collect"
    query: |
      AADNonInteractiveUserSignInLogs | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where ResultType == 0 | summarize TokenCount=count() by UserPrincipalName, AppDisplayName, IPAddress, ResourceDisplayName | order by TokenCount desc | take 50

  - node: "OAuth Abuse"
    stage: "analyze"
    query: |
      AuditLogs | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where OperationName in ("Consent to application","Add OAuth2PermissionGrant","Add app role assignment grant to user") | project TimeGenerated, InitiatedBy, TargetResources, AdditionalDetails | order by TimeGenerated asc

  - node: "OAuth Abuse"
    stage: "analyze"
    query: |
      CloudAppEvents | where Timestamp between (datetime(T_START) .. datetime(T_END)) | where ActionType == "Consent to application." | project Timestamp, AccountDisplayName, IPAddress, RawEventData | extend AppName=tostring(parse_json(RawEventData).Target[0].ID) | order by Timestamp asc

  - node: "OAuth Abuse"
    stage: "analyze"
    query: |
      AADServicePrincipalSignInLogs | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where AppId in ("<suspicious_app_id_1>","<suspicious_app_id_2>") | summarize AccessCount=count(), Resources=make_set(ResourceDisplayName) by AppId, ServicePrincipalName, IPAddress

  - node: "Revoke Cloud Sessions"
    stage: "contain"
    query: |
      SigninLogs | where TimeGenerated > ago(2h) | where UserPrincipalName == "compromised_user@domain.com" | where ResultType == 0 | project TimeGenerated, AppDisplayName, IPAddress, ClientAppUsed, DeviceDetail, ConditionalAccessStatus | order by TimeGenerated desc

  - node: "Revoke Cloud Sessions"
    stage: "contain"
    query: |
      AuditLogs | where TimeGenerated > ago(24h) | where OperationName in ("Revoke user all refresh tokens","Invalidate all refresh tokens for user","Disable device") | where InitiatedBy has "ir-admin" | project TimeGenerated, OperationName, TargetResources | order by TimeGenerated desc

  - node: "Revoke Cloud Sessions"
    stage: "contain"
    query: |
      CloudAppEvents | where Timestamp > ago(2h) | where AccountObjectId == "<user_object_id>" | summarize PostRevocationActivity=count() by ActionType, IPAddress | order by PostRevocationActivity desc

  - node: "Inbox Rules"
    stage: "analyze"
    query: |
      OfficeActivity | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where Operation in ("New-InboxRule","Set-InboxRule","Enable-InboxRule","UpdateInboxRules","Set-Mailbox","Set-TransportRule","New-TransportRule") | project TimeGenerated, UserId, Operation, Parameters, ClientIP | order by TimeGenerated asc

  - node: "Inbox Rules"
    stage: "analyze"
    query: |
      CloudAppEvents | where Timestamp between (datetime(T_START) .. datetime(T_END)) | where ActionType in ("Set-InboxRule","New-InboxRule","Set-Mailbox") | extend RuleDetails=tostring(RawEventData) | where RuleDetails has_any ("ForwardTo","RedirectTo","DeleteMessage","MoveToFolder") | project Timestamp, AccountDisplayName, ActionType, RuleDetails, IPAddress

  - node: "Inbox Rules"
    stage: "analyze"
    query: |
      AuditLogs | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where OperationName in ("Set-Mailbox","Add-MailboxPermission","Add-RecipientPermission") | project TimeGenerated, OperationName, InitiatedBy, TargetResources | order by TimeGenerated asc

  - node: "Missing Log Fallback"
    stage: "collect"
    query: |
      SecurityEvent | where TimeGenerated > ago(90d) | summarize MinTime=min(TimeGenerated), MaxTime=max(TimeGenerated), Count=count() by Computer, EventID | where MinTime > ago(30d) | project Computer, EventID, MinTime, GapDays=datetime_diff("day", now(), MinTime) | order by GapDays desc // Identify log coverage gaps

  - node: "Missing Log Fallback"
    stage: "collect"
    query: |
      DeviceEvents | where Timestamp > ago(30d) | summarize EarliestEvent=min(Timestamp), LatestEvent=max(Timestamp), EventCount=count() by DeviceName | where EarliestEvent > ago(14d) | project DeviceName, EarliestEvent, CoverageDays=datetime_diff("day", LatestEvent, EarliestEvent) // Find devices with short log retention

  - node: "Missing Log Fallback"
    stage: "collect"
    query: |
      index=* earliest=-90d | stats min(_time) as earliest max(_time) as latest count by sourcetype host | where earliest > relative_time(now(), "-30d") | table host sourcetype earliest latest count // SPL: Find hosts with recent-only logs

  - node: "Web Server Logs"
    stage: "collect"
    query: |
      index=web sourcetype=access_combined OR sourcetype=iis | where status >= 400 | stats count by uri_path, status, clientip | where count > 50 | sort -count // Find frequently targeted URIs

  - node: "Web Server Logs"
    stage: "collect"
    query: |
      index=web sourcetype=access_combined | where method="POST" | where uri_path=".php" OR uri_path=".aspx" OR uri_path=".jsp" | where NOT uri_path IN ("/login.php", "/api/*") | stats count values(clientip) by uri_path | where count < 10 // Find suspicious POST to uncommon files

  - node: "Web Server Logs"
    stage: "collect"
    query: |
      W3CIISLog | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where scStatus >= 200 and scStatus < 300 | where csUriStem endswith ".aspx" or csUriStem endswith ".asp" or csUriStem endswith ".ashx" | where csUriStem !in~ (KNOWN_GOOD_PAGES) | summarize RequestCount=count(), DistinctIPs=dcount(cIP) by csUriStem | where DistinctIPs == 1 // Find pages accessed by single IP (potential web shell)

  - node: "Malware Removal"
    stage: "eradicate"
    query: |
      DeviceFileEvents | where Timestamp > ago(1h) | where DeviceName == "CLEANED_HOST" | where FolderPath has_any ("\Temp\","\AppData\","\ProgramData\") | where ActionType == "FileCreated" | project Timestamp, FileName, FolderPath, InitiatingProcessFileName // Verify no new malicious files after cleanup

  - node: "Malware Removal"
    stage: "eradicate"
    query: |
      DeviceNetworkEvents | where Timestamp > ago(4h) | where DeviceName == "CLEANED_HOST" | where RemoteUrl in~ (KNOWN_C2_DOMAINS) or RemoteIP in (KNOWN_C2_IPS) | project Timestamp, RemoteUrl, RemoteIP, RemotePort, InitiatingProcessFileName // Verify no C2 beaconing post-cleanup

  - node: "Credential Reset"
    stage: "eradicate"
    query: |
      IdentityInfo | where AccountName in~ (COMPROMISED_ACCOUNTS) | project AccountName, AccountDomain, IsAccountEnabled, PasswordLastSet, LastLogonTimestamp // Verify account status

  - node: "Credential Reset"
    stage: "eradicate"
    query: |
      SigninLogs | where TimeGenerated > ago(1h) | where UserPrincipalName in~ (RESET_ACCOUNTS) | where ResultType == 0 | project TimeGenerated, UserPrincipalName, AppDisplayName, IPAddress, Location // Monitor for successful logins after reset (should be none)

  - node: "Credential Reset"
    stage: "eradicate"
    query: |
      AuditLogs | where TimeGenerated > ago(24h) | where OperationName == "Reset password" or OperationName == "Change password" | project TimeGenerated, InitiatedBy, TargetResources | order by TimeGenerated desc // Track password resets

  - node: "Patch Vulnerability"
    stage: "eradicate"
    query: |
      DeviceTvmSoftwareVulnerabilities | where CveId == "CVE-XXXX-XXXXX" | project DeviceName, SoftwareName, SoftwareVersion, VulnerabilitySeverityLevel | order by VulnerabilitySeverityLevel desc // Find all devices with the exploited vulnerability

  - node: "System Rebuild"
    stage: "recover"
    query: |
      DeviceInfo | where Timestamp > ago(1h) | where DeviceName == "REBUILT_HOST" | project DeviceName, OSPlatform, OSVersion, OnboardingStatus, SensorHealthState // Verify EDR enrollment after rebuild

  - node: "Service Restoration"
    stage: "recover"
    query: |
      DeviceNetworkEvents | where Timestamp > ago(4h) | where DeviceName in ("RESTORED_HOSTS") | where RemoteUrl !endswith ".microsoft.com" | summarize Connections=count() by RemoteUrl, RemoteIP | where Connections > 5 | order by Connections desc // Detect unexpected outbound from restored systems

  - node: "Service Restoration"
    stage: "recover"
    query: |
      SecurityEvent | where TimeGenerated > ago(4h) | where Computer in ("RESTORED_HOSTS") | where EventID in (4720, 4732, 7045, 4698) | project TimeGenerated, Computer, EventID, Activity // Alert on new accounts, services, tasks

  - node: "Backup Validation"
    stage: "recover"
    query: |
      Event | where Source == "VSS" or Source == "VolSnap" | where TimeGenerated between (datetime(T_START) .. now()) | where EventID in (8193, 8194, 36) | project TimeGenerated, Computer, EventID, RenderedDescription // Detect VSS deletion indicating backup tampering

  - node: "Detection Improvement"
    stage: "post-incident"
    query: |
      SecurityEvent | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where EventID == 4688 | where Process has_any (ATTACKER_TOOLS) | summarize count() by Process, CommandLine // Validate detection rule against incident data

  - node: "BEC Email Analysis"
    stage: "triage"
    query: |
      EmailEvents | where Timestamp between (datetime(T_START) .. datetime(T_END)) | where SenderFromDomain has_any (SUSPICIOUS_DOMAINS) | project Timestamp, SenderFromAddress, RecipientEmailAddress, Subject, DeliveryAction, AuthenticationDetails | order by Timestamp asc

  - node: "BEC Email Analysis"
    stage: "triage"
    query: |
      EmailUrlInfo | where Timestamp between (datetime(T_START) .. datetime(T_END)) | where Url has_any (SUSPICIOUS_DOMAINS) | join EmailEvents on NetworkMessageId | project Timestamp, SenderFromAddress, RecipientEmailAddress, Subject, Url, UrlDomain

  - node: "BEC Email Analysis"
    stage: "triage"
    query: |
      OfficeActivity | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where Operation == "MailItemsAccessed" or Operation == "SendAs" or Operation == "SendOnBehalf" | where UserId in~ (COMPROMISED_ACCOUNTS) | project TimeGenerated, UserId, Operation, ClientIP

  - node: "Web Shell Detection"
    stage: "analyze"
    query: |
      W3CIISLog | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where csUriStem has_any (".aspx",".asp",".php",".jsp",".jspx") | summarize RequestCount=count(), DistinctIPs=dcount(cIP), FirstSeen=min(TimeGenerated) by csUriStem | where DistinctIPs <= 2 and RequestCount > 5 | order by FirstSeen asc // Find web shells: few IPs, many requests

  - node: "Web Shell Detection"
    stage: "analyze"
    query: |
      DeviceFileEvents | where Timestamp between (datetime(T_START) .. datetime(T_END)) | where FolderPath has_any ("wwwroot","inetpub","www","html","htdocs") | where FileName endswith ".php" or FileName endswith ".aspx" or FileName endswith ".jsp" | where ActionType == "FileCreated" | project Timestamp, DeviceName, FileName, FolderPath, InitiatingProcessFileName

  - node: "Insider Containment"
    stage: "contain"
    query: |
      SigninLogs | where TimeGenerated > ago(7d) | where UserPrincipalName == "SUSPECT_USER" | summarize LoginCount=count(), DistinctIPs=dcount(IPAddress), Locations=make_set(Location) by AppDisplayName | order by LoginCount desc // Baseline suspect activity

  - node: "Insider Containment"
    stage: "contain"
    query: |
      OfficeActivity | where TimeGenerated > ago(7d) | where UserId == "SUSPECT_USER" | where Operation in ("FileDownloaded","FileAccessed","FileCopied","MailItemsAccessed") | summarize count() by Operation, bin(TimeGenerated, 1h) | order by TimeGenerated asc // Track data access patterns

  - node: "Insider Containment"
    stage: "contain"
    query: |
      DeviceFileEvents | where Timestamp > ago(7d) | where InitiatingProcessAccountName == "SUSPECT_USER" | where ActionType in ("FileCreated","FileModified","FileRenamed") | where FolderPath has_any ("USB","Removable","OneDrive","Dropbox","Google Drive") | project Timestamp, FileName, FolderPath, ActionType // Detect exfiltration to removable/cloud storage

  - node: "Credential Dumping"
    stage: "analyze"
    query: |
      DeviceProcessEvents | where Timestamp between (datetime(T_START) .. datetime(T_END)) | where FileName in~ ("mimikatz.exe","procdump.exe","procdump64.exe","sqldumper.exe","rundll32.exe","comsvcs.dll") or ProcessCommandLine has_any ("sekurlsa","lsadump","kerberos::list","privilege::debug","comsvcs","MiniDump") | project Timestamp, DeviceName, FileName, ProcessCommandLine, AccountName

  - node: "Credential Dumping"
    stage: "analyze"
    query: |
      SecurityEvent | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where EventID == 4662 | where Properties has "1131f6aa-9c07-11d1-f79f-00c04fc2dcd2" or Properties has "1131f6ad-9c07-11d1-f79f-00c04fc2dcd2" | where SubjectUserName != "MACHINE_ACCOUNT$" | project TimeGenerated, SubjectUserName, ObjectName, Properties // DCSync detection

  - node: "Credential Dumping"
    stage: "analyze"
    query: |
      SecurityEvent | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where EventID == 4769 | where TicketEncryptionType == "0x17" | where ServiceName !endswith "$" | project TimeGenerated, TargetUserName, ServiceName, IpAddress, TicketEncryptionType // Kerberoasting detection

  - node: "Persistence Hunt"
    stage: "eradicate"
    query: |
      DeviceRegistryEvents | where Timestamp between (datetime(T_START) .. datetime(T_END)) | where RegistryKey has_any ("Run","RunOnce","Winlogon","AppInit_DLLs","Image File Execution Options","Explorer\Shell Folders") | where ActionType in ("RegistryValueSet","RegistryKeyCreated") | project Timestamp, DeviceName, RegistryKey, RegistryValueName, RegistryValueData, InitiatingProcessFileName // Registry persistence

  - node: "Persistence Hunt"
    stage: "eradicate"
    query: |
      DeviceEvents | where Timestamp between (datetime(T_START) .. datetime(T_END)) | where ActionType in ("ScheduledTaskCreated","ScheduledTaskUpdated","ServiceInstalled","WmiBindingCreated") | project Timestamp, DeviceName, ActionType, FileName, AdditionalFields // Service/task/WMI persistence

  - node: "Persistence Hunt"
    stage: "eradicate"
    query: |
      AuditLogs | where TimeGenerated between (datetime(T_START) .. datetime(T_END)) | where OperationName in ("Add application","Add service principal","Add service principal credentials","Add delegated permission grant","Consent to application") | project TimeGenerated, OperationName, InitiatedBy, TargetResources // Cloud app persistence

  - node: "Eradication Verification"
    stage: "eradicate"
    query: |
      // KQL -- Verify no ransomware/malware re-execution post-eradication
      DeviceProcessEvents
      | where Timestamp > ago(48h)
      | where SHA256 in ("<HASH_1>","<HASH_2>","<HASH_3>")
      | project Timestamp, DeviceName, FileName, ProcessCommandLine, AccountName
      | sort by Timestamp desc

  - node: "Eradication Verification"
    stage: "eradicate"
    query: |
      // KQL -- Check for residual C2 communication
      DeviceNetworkEvents
      | where Timestamp > ago(48h)
      | where RemoteIP in ("<C2_IP_1>","<C2_IP_2>") or RemoteUrl has_any ("<C2_DOMAIN_1>","<C2_DOMAIN_2>")
      | project Timestamp, DeviceName, RemoteIP, RemoteUrl, InitiatingProcessFileName

  - node: "Eradication Verification"
    stage: "eradicate"
    query: |
      // PowerShell -- Verify credential resets across AD
      Get-ADUser -Filter * -Properties PasswordLastSet |
        Where-Object { $_.PasswordLastSet -lt (Get-Date).AddDays(-2) -and $_.SamAccountName -in @("<USER1>","<USER2>") } |
        Select-Object SamAccountName, PasswordLastSet, Enabled

  - node: "Config Hardening"
    stage: "eradicate"
    query: |
      // KQL -- Verify hardening changes took effect
      DeviceEvents
      | where Timestamp > ago(24h)
      | where ActionType startswith "Asr"
      | summarize BlockCount=count() by ActionType, DeviceName
      | sort by BlockCount desc

  - node: "Insider Evidence Capture"
    stage: "preserve"
    query: |
      // KQL -- Covert monitoring: suspect user file access patterns
      OfficeActivity
      | where UserId == "<SUSPECT_UPN>"
      | where TimeGenerated > ago(90d)
      | where Operation in ("FileDownloaded","FileUploaded","FileSyncDownloadedFull","FileAccessed")
      | summarize FileCount=count(), UniqueFiles=dcount(OfficeObjectId) by Operation, bin(TimeGenerated, 1d)
      | sort by TimeGenerated desc

  - node: "Insider Evidence Capture"
    stage: "preserve"
    query: |
      // KQL -- Suspect sign-in anomalies
      SigninLogs
      | where UserPrincipalName == "<SUSPECT_UPN>"
      | where TimeGenerated > ago(90d)
      | summarize SignInCount=count(), UniqueIPs=dcount(IPAddress), UniqueLocations=dcount(Location), UniqueApps=dcount(AppDisplayName) by bin(TimeGenerated, 1d)
      | sort by TimeGenerated desc

  - node: "Phishing Email Preservation"
    stage: "preserve"
    query: |
      // KQL -- Identify all recipients and click activity
      EmailEvents
      | where SenderFromAddress == "<PHISHING_SENDER>"
      | where Subject == "<PHISH_SUBJECT>"
      | where Timestamp > ago(7d)
      | project Timestamp, RecipientEmailAddress, DeliveryAction, AuthenticationDetails, SenderIPv4
      | join kind=leftouter (
          UrlClickEvents
          | where Timestamp > ago(7d)
          | project UrlClickTimestamp=Timestamp, AccountUpn, Url, ActionType
      ) on $left.RecipientEmailAddress == $right.AccountUpn
      | sort by Timestamp asc

  - node: "Phishing Triage"
    stage: "triage"
    query: |
      // KQL -- Delivery scope: all recipients of the phishing email
      EmailEvents
      | where SenderFromAddress == "<PHISHING_SENDER>"
      | where Subject has "<PHISH_SUBJECT>"
      | where Timestamp > ago(7d)
      | summarize RecipientCount=dcount(RecipientEmailAddress), Recipients=make_set(RecipientEmailAddress),
        Delivered=countif(DeliveryAction == "Delivered"), Blocked=countif(DeliveryAction == "Blocked") by SenderFromAddress, Subject

  - node: "Phishing Triage"
    stage: "triage"
    query: |
      // KQL -- URL click tracking for the phishing campaign
      UrlClickEvents
      | where Timestamp > ago(7d)
      | where Url has "<PHISHING_DOMAIN>"
      | project Timestamp, AccountUpn, Url, ActionType, IsClickedThrough
      | sort by Timestamp asc

  - node: "Phishing Quarantine"
    stage: "contain"
    query: |
      // KQL -- Verify purge effectiveness: check for remaining copies
      EmailEvents
      | where SenderFromAddress == "<PHISHING_SENDER>"
      | where Subject has "<PHISH_SUBJECT>"
      | where Timestamp > ago(7d)
      | where DeliveryAction == "Delivered"
      | where LatestDeliveryAction != "Moved to junk" and LatestDeliveryAction != "Removed"
      | project Timestamp, RecipientEmailAddress, DeliveryAction, LatestDeliveryAction

  - node: "Phishing Artifact Collection"
    stage: "collect"
    query: |
      // KQL -- Comprehensive phishing campaign artifact collection
      let phish_recipients = EmailEvents
      | where SenderFromAddress == "<PHISHING_SENDER>" and Subject has "<SUBJECT>"
      | distinct RecipientEmailAddress;
      let clickers = UrlClickEvents
      | where Url has "<PHISHING_DOMAIN>"
      | distinct AccountUpn;
      // Endpoint telemetry for clickers
      DeviceProcessEvents
      | where Timestamp > ago(7d)
      | where AccountUpn in (clickers) or InitiatingProcessAccountUpn in (clickers)
      | where InitiatingProcessFileName in~ ("outlook.exe","chrome.exe","msedge.exe","firefox.exe")
      | project Timestamp, DeviceName, AccountUpn, FileName, ProcessCommandLine
      | sort by Timestamp asc

  - node: "Phishing Artifact Collection"
    stage: "collect"
    query: |
      // KQL -- Post-click sign-in activity (credential compromise check)
      let clickers = UrlClickEvents
      | where Url has "<PHISHING_DOMAIN>"
      | distinct AccountUpn;
      SigninLogs
      | where Timestamp > ago(7d)
      | where UserPrincipalName in (clickers)
      | where ResultType == "0"
      | project Timestamp, UserPrincipalName, AppDisplayName, IPAddress, Location, DeviceDetail
      | sort by Timestamp asc

  - node: "Phishing Campaign Analysis"
    stage: "analyze"
    query: |
      // KQL -- Full phishing interaction chain analysis
      let delivered = EmailEvents
      | where SenderFromAddress == "<PHISHING_SENDER>" and Subject has "<SUBJECT>"
      | project RecipientEmailAddress, DeliveryTime=Timestamp;
      let clicked = UrlClickEvents
      | where Url has "<PHISHING_DOMAIN>"
      | project AccountUpn, ClickTime=Timestamp, IsClickedThrough;
      let compromised = SigninLogs
      | where IPAddress in ("<ATTACKER_IPS>")
      | where ResultType == "0"
      | project UserPrincipalName, LoginTime=TimeGenerated, IPAddress, AppDisplayName;
      delivered
      | join kind=leftouter (clicked) on $left.RecipientEmailAddress == $right.AccountUpn
      | join kind=leftouter (compromised) on $left.RecipientEmailAddress == $right.UserPrincipalName
      | project RecipientEmailAddress, DeliveryTime, ClickTime, IsClickedThrough, LoginTime, IPAddress, AppDisplayName

  - node: "Phishing Campaign Analysis"
    stage: "analyze"
    query: |
      // KQL -- Post-compromise activity by compromised users
      let compromised_users = dynamic(["<USER1>","<USER2>"]);
      let attacker_ips = dynamic(["<IP1>","<IP2>"]);
      OfficeActivity
      | where UserId in (compromised_users)
      | where ClientIP in (attacker_ips)
      | summarize Actions=count(), UniqueOps=dcount(Operation), Operations=make_set(Operation) by UserId, ClientIP, bin(TimeGenerated, 1h)
      | sort by TimeGenerated asc

  - node: "Phishing Campaign Analysis"
    stage: "analyze"
    query: |
      // KQL -- Lateral phishing detection (compromised accounts sending phishing)
      EmailEvents
      | where Timestamp > ago(7d)
      | where SenderFromAddress in ("<COMPROMISED_USER_1>","<COMPROMISED_USER_2>")
      | where DeliveryAction == "Delivered"
      | project Timestamp, SenderFromAddress, RecipientEmailAddress, Subject, UrlCount, AttachmentCount
      | sort by Timestamp desc

  - node: "Phishing Remediation"
    stage: "eradicate"
    query: |
      // PowerShell -- Remove attacker persistence from all compromised accounts
      $compromised = @("<USER1>","<USER2>","<USER3>")
      foreach ($user in $compromised) {
        # Remove suspicious inbox rules
        Get-InboxRule -Mailbox $user | Where-Object { $_.ForwardTo -or $_.RedirectTo -or $_.DeleteMessage } | Remove-InboxRule -Confirm:$false
        # Revoke OAuth grants
        Get-AzureADUserOAuth2PermissionGrant -ObjectId $user | Where-Object { $_.ConsentType -eq "Principal" } | Remove-AzureADOAuth2PermissionGrant
        # Force password reset and session revocation
        Set-AzureADUser -ObjectId $user -PasswordProfile @{ForceChangePasswordNextLogin=$true}
        Revoke-AzureADUserAllRefreshToken -ObjectId $user
        Write-Host "Remediated: $user"
      }

  - node: "Phishing Remediation"
    stage: "eradicate"
    query: |
      // KQL -- Post-remediation monitoring rule
      let blocked_infra = dynamic(["<C2_IP1>","<C2_IP2>","<PHISH_DOMAIN>"]);
      let compromised_users = dynamic(["<USER1>","<USER2>"]);
      union SigninLogs, DeviceNetworkEvents, OfficeActivity
      | where TimeGenerated > ago(24h)
      | where (IPAddress in (blocked_infra) or RemoteIP in (blocked_infra) or ClientIP in (blocked_infra))
          or (UserPrincipalName in (compromised_users) or UserId in (compromised_users))
      | project TimeGenerated, Type, UserPrincipalName, IPAddress, OperationName

  - node: "Ransomware Resilience Review"
    stage: "post-incident"
    query: |
      DeviceProcessEvents | where Timestamp > ago(30d) | where FileName in~ ("vssadmin.exe","wbadmin.exe","bcdedit.exe","psexec.exe") | summarize count() by FileName, InitiatingProcessAccountName, DeviceName

  - node: "Ransomware Resilience Review"
    stage: "post-incident"
    query: |
      CommonSecurityLog | where TimeGenerated > ago(30d) | where DestinationPort in (445, 135, 3389, 5985, 5986) | summarize Attempts=count(), Hosts=make_set(SourceIP) by DestinationPort

  - node: "Cloud Hardening Review"
    stage: "post-incident"
    query: |
      AuditLogs | where TimeGenerated > ago(30d) | where OperationName has_any ("Add service principal credentials", "Consent to application", "Add member to role") | project TimeGenerated, OperationName, InitiatedBy, TargetResources

  - node: "Cloud Hardening Review"
    stage: "post-incident"
    query: |
      AzureActivity | where TimeGenerated > ago(30d) | where ResourceProvider has "Microsoft.ContainerService" or ResourceProvider has "Microsoft.ContainerRegistry" | summarize count() by OperationNameValue, Caller, ResourceGroup

  - node: "Disclosure Review"
    stage: "post-incident"
    query: |
      OfficeActivity | where TimeGenerated > ago(30d) | where Operation in ("FileDownloaded", "FileAccessed", "MailItemsAccessed") | summarize count() by UserId, Operation, bin(TimeGenerated, 1h)

  - node: "Disclosure Review"
    stage: "post-incident"
    query: |
      CommonSecurityLog | where TimeGenerated > ago(30d) | where SentBytes > 10000000 | summarize TotalBytes=sum(SentBytes) by SourceIP, DestinationHostName, ApplicationProtocol

  - node: "Web App Root Cause Review"
    stage: "post-incident"
    query: |
      AzureDiagnostics | where TimeGenerated > ago(30d) | where ResourceType has "APPLICATIONGATEWAYS" or Category has "ApplicationGatewayFirewallLog" | summarize count() by RuleId, clientIP_s, requestUri_s
