Initial commit: KQL ↔ SDL PowerQuery proof of equivalence

This commit is contained in:
marc
2026-06-01 09:57:14 +02:00
commit 23cbaa9c08
91 changed files with 5966 additions and 0 deletions
@@ -0,0 +1,22 @@
SigninLogs
| where TimeGenerated > ago(1d)
| extend locationString = strcat(tostring(LocationDetails["countryOrRegion"]), "/",
tostring(LocationDetails["state"]), "/",
tostring(LocationDetails["city"]), ";")
| project TimeGenerated, AppDisplayName, UserPrincipalName, locationString
| make-series dLocationCount = dcount(locationString) on TimeGenerated step 1d
by UserPrincipalName, AppDisplayName
| extend (RSquare, Slope, Variance, RVariance, Interception, LineFit)
= series_fit_line(dLocationCount)
| top 3 by Slope desc
| join kind=inner (
SigninLogs
| extend locationString = strcat(tostring(LocationDetails["countryOrRegion"]),
"/", tostring(LocationDetails["state"]), "/",
tostring(LocationDetails["city"]), ";")
| summarize locationList = makeset(locationString),
threeDayWindowLocationCount = dcount(locationString)
by AppDisplayName, UserPrincipalName, timerange = bin(TimeGenerated, 21d)
) on AppDisplayName, UserPrincipalName
| project timerange, AppDisplayName, UserPrincipalName,
threeDayWindowLocationCount, locationList
+13
View File
@@ -0,0 +1,13 @@
let auditLookback = ago(14d);
let baseline = AuditLogs
| where TimeGenerated between(auditLookback..ago(1d))
| extend InitiatedByApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName)
| where isnotempty(InitiatedByApp)
| summarize by OperationName, InitiatedByApp;
AuditLogs
| where TimeGenerated >= ago(1d)
| extend InitiatedByApp = tostring(parse_json(tostring(InitiatedBy.app)).displayName)
| extend InitiatedByUser = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| extend Actor = iff(isnotempty(InitiatedByApp), InitiatedByApp, InitiatedByUser)
| where isnotempty(Actor)
| join kind=leftanti baseline on $left.OperationName == $right.OperationName
+11
View File
@@ -0,0 +1,11 @@
let SensitiveOps = dynamic([
"microsoft.compute/snapshots/write",
"microsoft.network/networksecuritygroups/write",
"microsoft.storage/storageaccounts/listkeys/action"]);
let threshold = 5;
AzureActivity
| where OperationNameValue in~ (SensitiveOps)
| where ActivityStatusValue =~ "Success"
| where TimeGenerated >= ago(1d)
| summarize ActivityCount = count() by CallerIpAddress, Caller, OperationNameValue
| where ActivityCount >= threshold
+10
View File
@@ -0,0 +1,10 @@
SigninLogs
| where TimeGenerated > ago(1d)
| extend locationString = strcat(tostring(LocationDetails["countryOrRegion"]), "/",
tostring(LocationDetails["state"]), "/", tostring(LocationDetails["city"]), ";")
| extend Day = format_datetime(TimeGenerated, "yyyy-MM-dd")
| summarize LocationList = make_set(locationString),
LocationCount = dcount(locationString),
DistinctSourceIp = dcount(IPAddress),
LogonCount = count()
by Day, AppDisplayName, UserPrincipalName
@@ -0,0 +1,7 @@
CommonSecurityLog
| where TimeGenerated > ago(1d)
| summarize Count = count(),
DistinctDestinationIps = dcount(DestinationIP),
NoofBytesTransferred = sum(SentBytes),
NoofBytesReceived = sum(ReceivedBytes)
by SourceIP, DeviceVendor
+9
View File
@@ -0,0 +1,9 @@
SecurityEvent
| where TimeGenerated > ago(1d)
| where EventID == 4688
| summarize Count = count(),
DistinctComputers = dcount(Computer),
DistinctAccounts = dcount(Account),
DistinctParent = dcount(ParentProcessName),
NoofCommandLines = dcount(CommandLine)
by NewProcessName
+9
View File
@@ -0,0 +1,9 @@
let timeframe = 1d; let lookback = 7d;
let Recent = SigninLogs | where TimeGenerated > ago(timeframe) | where ResultType == 0;
let Baseline = SigninLogs
| where TimeGenerated between(ago(lookback + timeframe) .. ago(timeframe))
| where ResultType == 0
| summarize by AppDisplayName, UserAgent;
Recent
| join kind=leftanti Baseline on AppDisplayName, UserAgent
| project TimeGenerated, UserPrincipalName, AppDisplayName, UserAgent
+9
View File
@@ -0,0 +1,9 @@
let IP_Indicators = ThreatIntelIndicators
| extend IndicatorType = tostring(split(ObservableKey, ":", 0)[0])
| where IndicatorType in ("ipv4-addr", "ipv6-addr", "network-traffic")
| where IsActive == true;
IP_Indicators
| join kind=innerunique (
CommonSecurityLog | where TimeGenerated >= ago(1h)
) on $left.ObservableValue == $right.DestinationIP
| project TimeGenerated, SourceIP, DestinationIP, Id, Confidence, DeviceVendor
+8
View File
@@ -0,0 +1,8 @@
let baseline = SecurityEvent
| where TimeGenerated between (ago(14d) .. ago(1d))
| where EventID == 4688
| summarize by FileName = tostring(split(NewProcessName, '\\')[-1]);
SecurityEvent
| where TimeGenerated >= ago(1d) | where EventID == 4688
| extend FileName = tostring(split(NewProcessName, '\\')[-1])
| join kind=leftanti baseline on FileName
+14
View File
@@ -0,0 +1,14 @@
let threshold = 25;
let baseline = OfficeActivity
| where TimeGenerated between(ago(14d) .. ago(1d))
| where RecordType == "SharePointFileOperation"
| where Operation in ("FileDownloaded", "FileUploaded")
| summarize Count = count() by UserId, Operation, Site_Url, ClientIP
| summarize AvgCount = avg(Count) by UserId, Operation, Site_Url, ClientIP;
let recent = OfficeActivity
| where TimeGenerated > ago(1d)
| where RecordType == "SharePointFileOperation"
| summarize RecentCount = count() by UserId, Operation, Site_Url, ClientIP;
baseline | join kind=inner (recent) on UserId, Operation, Site_Url, ClientIP
| extend Deviation = abs(RecentCount - AvgCount) / AvgCount
| where Deviation > threshold
+11
View File
@@ -0,0 +1,11 @@
let TotalEventsThreshold = 30; let PercentBeaconThreshold = 80;
CommonSecurityLog
| where DeviceVendor == "Palo Alto Networks" and Activity == "TRAFFIC"
| where TimeGenerated > ago(1d)
| sort by SourceIP asc, TimeGenerated asc
| serialize | extend nextT = next(TimeGenerated, 1), nextIP = next(SourceIP, 1)
| extend Delta = datetime_diff('second', nextT, TimeGenerated)
| where SourceIP == nextIP and Delta > 25
| summarize TotalEvents = count(), ModalDelta = arg_max(count(), Delta)
by SourceIP, DestinationIP, DestinationPort
| where TotalEvents > TotalEventsThreshold
@@ -0,0 +1,13 @@
let baseline = SecurityEvent
| where TimeGenerated between (ago(14d) .. ago(1d))
| where EventID in (4624, 4625)
| where LogonTypeName in~ ("2 - Interactive", "10 - RemoteInteractive")
| where AccountType =~ "User"
| extend HourOfLogin = hourofday(TimeGenerated)
| summarize MaxHour = max(HourOfLogin), MinHour = min(HourOfLogin) by TargetUserName;
SecurityEvent
| where TimeGenerated >= ago(1d) | where EventID in (4624, 4625)
| where LogonTypeName in~ ("2 - Interactive", "10 - RemoteInteractive")
| extend HourOfLogin = hourofday(TimeGenerated)
| join kind=inner baseline on TargetUserName
| where HourOfLogin > MaxHour or HourOfLogin < MinHour
@@ -0,0 +1,7 @@
DeviceFileEvents
| where FileName endswith ".docx" or FileName endswith ".pdf" or FileName endswith ".xlsx"
| where FolderPath contains "Confidential" or FolderPath contains "Sensitive"
or FolderPath contains "Restricted"
| where ActionType in ("FileAccessed","FileRead","FileModified","FileCopied","FileMoved")
| extend User = tostring(InitiatingProcessAccountName)
| summarize AccessCount = count() by FileName, User
+8
View File
@@ -0,0 +1,8 @@
AuditLogs
| where TimeGenerated > ago(1d)
| where OperationName has_any ("Add service principal","Certificates and secrets management")
| extend Actor = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)
| join kind=inner (
SigninLogs | where ResultType == 0 and TimeGenerated > ago(1d)
| project LoginTime = TimeGenerated, Identity, IPAddress, AppDisplayName
) on $left.Actor == $right.Identity
+7
View File
@@ -0,0 +1,7 @@
let codes = dynamic([50053,50126,50055,50057,50155,50105,50133,50005,50076,
50079,50173,50158,50072,50074,53003,53000,53001,50129]);
SigninLogs
| where TimeGenerated > ago(1d) | where ResultType in (codes)
| summarize FailedAttempts = count(), UniqueUsers = dcount(UserPrincipalName)
by IPAddress
| where FailedAttempts > 5 and UniqueUsers > 5
+3
View File
@@ -0,0 +1,3 @@
SigninLogs | where TimeGenerated > ago(1d) | where ResultType == 0
| summarize CountriesAccessed = make_set(Location) by UserPrincipalName
| where array_length(CountriesAccessed) > 3
+9
View File
@@ -0,0 +1,9 @@
let historical = SigninLogs
| where ResultType == 0
| where TimeGenerated between (ago(14d) .. ago(1d))
| summarize HistoricalCountries = make_set(Location) by UserPrincipalName;
SigninLogs | where ResultType == 0 | where TimeGenerated > ago(1d)
| summarize TodayCountries = make_set(Location) by UserPrincipalName
| join kind=inner (historical) on UserPrincipalName
| extend NewLocations = set_difference(TodayCountries, HistoricalCountries)
| where array_length(NewLocations) > 0