This commit is contained in:
Pete Bryan 2021-08-06 13:39:23 -07:00
Родитель b1faf7dc83
Коммит 8c900dafa2
11 изменённых файлов: 73 добавлений и 74 удалений

Просмотреть файл

@ -1,7 +1,7 @@
id: d0ae35df-0eaf-491f-b23e-8190e4f3ffe9 id: d0ae35df-0eaf-491f-b23e-8190e4f3ffe9
name: Rare process running on a Linux host name: Rare process running on a Linux host
description: | description: |
'Looks for rare processes that are running on Linux hosts. Looks for process seen less than 14 times in last 7 days, 'Looks for rare processes that are running on Linux hosts. Looks for process seen less than 14 times in last 7 days,
or observed rate is less than 1% of of the average for the environment and fewer than 100.' or observed rate is less than 1% of of the average for the environment and fewer than 100.'
requiredDataConnectors: requiredDataConnectors:
- connectorId: Syslog - connectorId: Syslog
@ -15,26 +15,27 @@ relevantTechniques:
- T1053 - T1053
- T1037 - T1037
query: | query: |
let starttime = 7d;
let endtime = 1m; let starttime = todatetime('{{StartTimeISO}}');
let lookback = 30d; let endtime = todatetime('{{EndTimeISO}}');
let lookback = starttime - 14d;
let count_threshold = 100; let count_threshold = 100;
let perc_threshold = 0.01; let perc_threshold = 0.01;
let host_threshold = 14; let host_threshold = 14;
let basic=materialize( let basic=materialize(
Syslog Syslog
| where TimeGenerated >= ago(lookback) | where TimeGenerated >= lookback
| summarize FullCount = count() | summarize FullCount = count()
, Count= countif(TimeGenerated between (ago(starttime) .. ago(endtime))) , Count= countif(TimeGenerated between (starttime .. endtime))
, min_TimeGenerated=min(TimeGenerated) , min_TimeGenerated=min(TimeGenerated)
, max_TimeGenerated=max(TimeGenerated) , max_TimeGenerated=max(TimeGenerated)
by Computer, ProcessName by Computer, ProcessName
| where Count > 0 and Count < count_threshold); | where Count > 0 and Count < count_threshold);
let basic_avg = basic let basic_avg = basic
| summarize Avg = avg(FullCount) by ProcessName; | summarize Avg = avg(FullCount) by ProcessName;
basic | project-away FullCount basic | project-away FullCount
| join kind=inner | join kind=inner
basic_avg basic_avg
on ProcessName | project-away ProcessName1 on ProcessName | project-away ProcessName1
| where Count < host_threshold or (Count <= Avg*perc_threshold and Count < count_threshold) | where Count < host_threshold or (Count <= Avg*perc_threshold and Count < count_threshold)
| extend HostCustomEntity=Computer | extend HostCustomEntity=Computer

Просмотреть файл

@ -1,8 +1,8 @@
id: 959fe0f0-7ac0-467c-944f-5b8c6fdc9e72 id: 959fe0f0-7ac0-467c-944f-5b8c6fdc9e72
name: Disabled accounts using Squid proxy name: Disabled accounts using Squid proxy
description: | description: |
'Look for accounts that have a been recorded as disabled by AD in the previous week but are still using the proxy during 'Look for accounts that have a been recorded as disabled by AD in the previous time period but are still using the proxy during
the current week. This query presumes the default squid log format is being used. http://www.squid-cache.org/Doc/config/access_log/' the current time period. This query presumes the default squid log format is being used. http://www.squid-cache.org/Doc/config/access_log/'
requiredDataConnectors: requiredDataConnectors:
- connectorId: Syslog - connectorId: Syslog
dataTypes: dataTypes:
@ -13,17 +13,18 @@ relevantTechniques:
- T1110 - T1110
query: | query: |
let starttime = 14d; let starttime = todatetime('{{StartTimeISO}}');
let endtime = 7d; let endtime = todatetime('{{EndTimeISO}}');
let lookback = startime - 14d;
let disabledAccounts = (){ let disabledAccounts = (){
SigninLogs SigninLogs
| where TimeGenerated between(ago(starttime) .. ago(endtime)) | where TimeGenerated between(lookback..starttime))
| where ResultType == 50057 | where ResultType == 50057
| where ResultDescription =~ "User account is disabled. The account has been disabled by an administrator." | where ResultDescription =~ "User account is disabled. The account has been disabled by an administrator."
}; };
let proxyEvents = (){ let proxyEvents = (){
Syslog Syslog
| where TimeGenerated > ago(endtime) | where TimeGenerated between(starttime..endtime)
| where ProcessName contains "squid" | where ProcessName contains "squid"
| extend URL = extract("(([A-Z]+ [a-z]{4,5}:\\/\\/)|[A-Z]+ )([^ :]*)",3,SyslogMessage), | extend URL = extract("(([A-Z]+ [a-z]{4,5}:\\/\\/)|[A-Z]+ )([^ :]*)",3,SyslogMessage),
SourceIP = extract("([0-9]+ )(([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3}))",2,SyslogMessage), SourceIP = extract("([0-9]+ )(([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3}))",2,SyslogMessage),

Просмотреть файл

@ -2,11 +2,11 @@ id: e472c490-4792-4f12-8b6b-6ab3e0404d35
name: Squid data volume timeseries anomalies name: Squid data volume timeseries anomalies
description: | description: |
'Malware infections or data exfiltration activity often leads to anomalies in network data volume 'Malware infections or data exfiltration activity often leads to anomalies in network data volume
this hunting query looks for anomalies in the volume of bytes traversing a squid proxy. Anomalies require further this hunting query looks for anomalies in the volume of bytes traversing a squid proxy. Anomalies require further
investigation to determine cause. This query presumes the default squid log format is being used.' investigation to determine cause. This query presumes the default squid log format is being used.'
requiredDataConnectors: requiredDataConnectors:
- connectorId: Syslog - connectorId: Syslog
dataTypes: dataTypes:
- Syslog - Syslog
tactics: tactics:
- CommandAndControl - CommandAndControl
@ -16,16 +16,12 @@ relevantTechniques:
- T1030 - T1030
query: | query: |
let starttime = 14d; let TimeSeriesData =
let endtime = 1d;
let timeframe = 1h;
let TimeSeriesData =
Syslog Syslog
| where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))
| where ProcessName contains "squid" | where ProcessName contains "squid"
| extend URL = extract("(([A-Z]+ [a-z]{4,5}:\\/\\/)|[A-Z]+ )([^ :]*)",3,SyslogMessage), | extend URL = extract("(([A-Z]+ [a-z]{4,5}:\\/\\/)|[A-Z]+ )([^ :]*)",3,SyslogMessage),
SourceIP = extract("([0-9]+ )(([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3}))",2,SyslogMessage), SourceIP = extract("([0-9]+ )(([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3}))",2,SyslogMessage),
Status = extract("(TCP_(([A-Z]+)(_[A-Z]+)*)|UDP_(([A-Z]+)(_[A-Z]+)*))",1,SyslogMessage), Status = extract("(TCP_(([A-Z]+)(_[A-Z]+)*)|UDP_(([A-Z]+)(_[A-Z]+)*))",1,SyslogMessage),
HTTP_Status_Code = extract("(TCP_(([A-Z]+)(_[A-Z]+)*)|UDP_(([A-Z]+)(_[A-Z]+)*))/([0-9]{3})",8,SyslogMessage), HTTP_Status_Code = extract("(TCP_(([A-Z]+)(_[A-Z]+)*)|UDP_(([A-Z]+)(_[A-Z]+)*))/([0-9]{3})",8,SyslogMessage),
User = extract("(CONNECT |GET )([^ ]* )([^ ]+)",3,SyslogMessage), User = extract("(CONNECT |GET )([^ ]* )([^ ]+)",3,SyslogMessage),
RemotePort = extract("(CONNECT |GET )([^ ]*)(:)([0-9]*)",4,SyslogMessage), RemotePort = extract("(CONNECT |GET )([^ ]*)(:)([0-9]*)",4,SyslogMessage),
@ -39,4 +35,3 @@ query: |
| extend (anomalies, score, baseline) = series_decompose_anomalies(TotalBytesSent,3, -1, 'linefit') | extend (anomalies, score, baseline) = series_decompose_anomalies(TotalBytesSent,3, -1, 'linefit')
| extend timestamp = TimeGenerated | extend timestamp = TimeGenerated
| render timechart with (title="Squid Time Series anomalies") | render timechart with (title="Squid Time Series anomalies")

Просмотреть файл

@ -17,14 +17,15 @@ tactics:
- Impact - Impact
query: | query: |
let dt_lookBack = 1h; let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let ioc_lookBack = 14d; let ioc_lookBack = 14d;
ThreatIntelligenceIndicator ThreatIntelligenceIndicator
| where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now() | where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now()
| where Active == true | where Active == true
| where isnotempty(FileName) | where isnotempty(FileName)
| join ( | join (
OfficeActivity| where TimeGenerated >= ago(dt_lookBack) OfficeActivity| where TimeGenerated between(starttime..endtime)
| where isnotempty(SourceFileName) | where isnotempty(SourceFileName)
| extend OfficeActivity_TimeGenerated = TimeGenerated | extend OfficeActivity_TimeGenerated = TimeGenerated
) )

Просмотреть файл

@ -17,14 +17,15 @@ tactics:
- Impact - Impact
query: | query: |
let dt_lookBack = 1h; let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let ioc_lookBack = 14d; let ioc_lookBack = 14d;
ThreatIntelligenceIndicator ThreatIntelligenceIndicator
| where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now() | where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now()
| where Active == true | where Active == true
| where isnotempty(FileName) | where isnotempty(FileName)
| join ( | join (
SecurityEvent | where TimeGenerated >= ago(dt_lookBack) SecurityEvent | where TimeGenerated between(starttime..endtime)
| where EventID in ("4688","8002","4648","4673") | where EventID in ("4688","8002","4648","4673")
| where isnotempty(Process) | where isnotempty(Process)
| extend SecurityEvent_TimeGenerated = TimeGenerated, Event = EventID | extend SecurityEvent_TimeGenerated = TimeGenerated, Event = EventID

Просмотреть файл

@ -17,7 +17,8 @@ tactics:
- Impact - Impact
query: | query: |
let dt_lookBack = 1h; let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let ioc_lookBack = 14d; let ioc_lookBack = 14d;
ThreatIntelligenceIndicator ThreatIntelligenceIndicator
| where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now() | where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now()
@ -25,7 +26,7 @@ query: |
| where isnotempty(FileName) | where isnotempty(FileName)
| extend TI_ProcessEntity = tostring(split(FileName, ".")[-2]) | extend TI_ProcessEntity = tostring(split(FileName, ".")[-2])
| join ( | join (
Syslog | where TimeGenerated >= ago(dt_lookBack) Syslog | where TimeGenerated between(starttime..endtime)
| where isnotempty(ProcessName) | where isnotempty(ProcessName)
| extend Syslog_TimeGenerated = TimeGenerated | extend Syslog_TimeGenerated = TimeGenerated
) )

Просмотреть файл

@ -17,7 +17,8 @@ tactics:
- Impact - Impact
query: | query: |
let dt_lookBack = 1h; let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let ioc_lookBack = 14d; let ioc_lookBack = 14d;
ThreatIntelligenceIndicator ThreatIntelligenceIndicator
| where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now() | where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now()
@ -25,7 +26,7 @@ query: |
| where isnotempty(FileName) | where isnotempty(FileName)
| extend TI_ProcessEntity = tostring(split(FileName, ".")[-2]) | extend TI_ProcessEntity = tostring(split(FileName, ".")[-2])
| join ( | join (
VMConnection | where TimeGenerated >= ago(dt_lookBack) VMConnection | where TimeGenerated between(starttime..endtime)
| where isnotempty(ProcessName) | where isnotempty(ProcessName)
| extend VMConnection_TimeGenerated = TimeGenerated | extend VMConnection_TimeGenerated = TimeGenerated
) )

Просмотреть файл

@ -17,14 +17,15 @@ tactics:
- Impact - Impact
query: | query: |
let dt_lookBack = 1h; let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let ioc_lookBack = 14d; let ioc_lookBack = 14d;
ThreatIntelligenceIndicator ThreatIntelligenceIndicator
| where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now() | where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now()
| where Active == true | where Active == true
| where isnotempty(FileName) | where isnotempty(FileName)
| join ( | join (
WireData | where TimeGenerated >= ago(dt_lookBack) WireData | where TimeGenerated between(starttime..endtime)
| where isnotempty(ProcessName) | where isnotempty(ProcessName)
| extend Process =reverse(substring(reverse(ProcessName), 0, indexof(reverse(ProcessName), "\\"))) | extend Process =reverse(substring(reverse(ProcessName), 0, indexof(reverse(ProcessName), "\\")))
| extend WireData_TimeGenerated = TimeGenerated | extend WireData_TimeGenerated = TimeGenerated

Просмотреть файл

@ -2,8 +2,8 @@ id: e0c947c3-fe83-46ff-bbda-a43224a785fd
name: Web Shell Activity name: Web Shell Activity
description: | description: |
'Web shells are scripts that, when uploaded to a web server, can be used to provide a backdoor to a compromised network. 'Web shells are scripts that, when uploaded to a web server, can be used to provide a backdoor to a compromised network.
Attackers can use this entry point to leave malicious implants, such as obtaining unauthorized access, escalating privilege, and further compromising the environment. Attackers can use this entry point to leave malicious implants, such as obtaining unauthorized access, escalating privilege, and further compromising the environment.
This query hunts for web shells by analysing the distribution of commonly-used web shell scripts against regular scripts for those public client IPs which have not observed any W3CIIS activity in a fixed lookback period. This query hunts for web shells by analysing the distribution of commonly-used web shell scripts against regular scripts for those public client IPs which have not observed any W3CIIS activity in a fixed lookback period.
The results obtained summarise the public client IPs, user agents, and the distribution of the above scripts between the start and end time.' The results obtained summarise the public client IPs, user agents, and the distribution of the above scripts between the start and end time.'
requiredDataConnectors: requiredDataConnectors:
@ -15,25 +15,25 @@ tactics:
- InitialAccess - InitialAccess
relevantTechniques: relevantTechniques:
- T1100 - T1100
query: | query: |
let end_time = now(); let starttime = todatetime('{{StartTimeISO}}');
let start_time = end_time - timespan(1d); let endtime = todatetime('{{EndTimeISO}}');
let lookback_time_from_start_time = timespan(3d); let lookback = starttime - (3d);
let script_extensions = dynamic([".asp", ".aspx", ".armx", ".asax", ".ashz", ".asmx", ".axd", ".cshtml", ".php", ".phps", ".php3", ".php4", ".php5", ".php7", ".jsp", ".jspx", ".cfm", ".cfml", ".phtml"]); let script_extensions = dynamic([".asp", ".aspx", ".armx", ".asax", ".ashz", ".asmx", ".axd", ".cshtml", ".php", ".phps", ".php3", ".php4", ".php5", ".php7", ".jsp", ".jspx", ".cfm", ".cfml", ".phtml"]);
let ignore_uristems = dynamic(["/ews/exchange.asmx"]); let ignore_uristems = dynamic(["/ews/exchange.asmx"]);
let lookback_period = ( let lookback_period = (
W3CIISLog W3CIISLog
| where TimeGenerated between ((start_time - lookback_time_from_start_time).. start_time) | where TimeGenerated between (lookback .. starttime)
| where not(ipv4_is_private(cIP)) and cIP != "127.0.0.1" | where not(ipv4_is_private(cIP)) and cIP != "127.0.0.1"
| summarize count() by cIP, csUserAgent | summarize count() by cIP, csUserAgent
| project cIP, csUserAgent | project cIP, csUserAgent
); );
let potential_webshell_activity = (W3CIISLog let potential_webshell_activity = (W3CIISLog
| where TimeGenerated between (start_time .. end_time) | where TimeGenerated between (starttime .. endtime)
| extend csUriStem = tolower(csUriStem) | extend csUriStem = tolower(csUriStem)
| where csUriStem matches regex "\\.[a-zA-Z][a-zA-Z0-9]+$" | where csUriStem matches regex "\\.[a-zA-Z][a-zA-Z0-9]+$"
| where csUriStem !in~ (ignore_uristems) // Remove noisy uri stems in the final results by editing the ignore_uristems variable | where csUriStem !in~ (ignore_uristems) // Remove noisy uri stems in the final results by editing the ignore_uristems variable
| extend suffix = strcat(".", split(split(csUriStem, "/")[-1], ".")[-1]) | extend suffix = strcat(".", split(split(csUriStem, "/")[-1], ".")[-1])
| extend is_script = iff(suffix in (script_extensions), 1, 0) | extend is_script = iff(suffix in (script_extensions), 1, 0)
| where not(ipv4_is_private(cIP)) and cIP != "127.0.0.1" | where not(ipv4_is_private(cIP)) and cIP != "127.0.0.1"

Просмотреть файл

@ -2,10 +2,10 @@ id: 33aa0e01-87e2-43ea-87f9-2f7e3ff1d532
name: Detect beacon like pattern based on repetitive time intervals in Wire Data Traffic name: Detect beacon like pattern based on repetitive time intervals in Wire Data Traffic
description: | description: |
'This query will identify beaconing patterns from Wire Data logs based on timedelta patterns. The query leverages various KQL functions 'This query will identify beaconing patterns from Wire Data logs based on timedelta patterns. The query leverages various KQL functions
to calculate time delta and then compare it with total events observed in a day to find percentage of beaconing. to calculate time delta and then compare it with total events observed in a day to find percentage of beaconing.
Results of such beaconing patterns to untrusted public networks can be a good starting point for investigation. Results of such beaconing patterns to untrusted public networks can be a good starting point for investigation.
References: Blog about creating dataset to identify network beaconing via repetitive time intervals seen against total traffic References: Blog about creating dataset to identify network beaconing via repetitive time intervals seen against total traffic
between same source-destination pair. between same source-destination pair.
http://www.austintaylor.io/detect/beaconing/intrusion/detection/system/command/control/flare/elastic/stack/2017/06/10/detect-beaconing-with-flare-elasticsearch-and-intrusion-detection-systems/' http://www.austintaylor.io/detect/beaconing/intrusion/detection/system/command/control/flare/elastic/stack/2017/06/10/detect-beaconing-with-flare-elasticsearch-and-intrusion-detection-systems/'
requiredDataConnectors: requiredDataConnectors:
- connectorId: AzureMonitor(WireData) - connectorId: AzureMonitor(WireData)
@ -18,30 +18,28 @@ relevantTechniques:
- T1065 - T1065
query: | query: |
let starttime = 7d; let lookback = 1d;
let endtime = 1d; let TimeDeltaThreshold = 10;
let TimeDeltaThreshold = 10; let TotalEventsThreshold = 15;
let TotalEventsThreshold = 15; let PercentBeaconThreshold = 95;
let PercentBeaconThreshold = 95; let PrivateIPregex = @'^127\.|^10\.|^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[0-1]\.|^192\.168\.';
let PrivateIPregex = @'^127\.|^10\.|^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[0-1]\.|^192\.168\.';
WireData WireData
| where TimeGenerated between (ago(starttime)..ago(endtime)) | where TimeGenerated > lookback
| extend RemoteIPType = iff(RemoteIP matches regex PrivateIPregex,"private" ,"public" ) | extend RemoteIPType = iff(RemoteIP matches regex PrivateIPregex,"private" ,"public" )
| where RemoteIPType =="public" | where RemoteIPType =="public"
| project TimeGenerated , LocalIP , LocalPortNumber , RemoteIP, RemotePortNumber, ReceivedBytes, SentBytes | project TimeGenerated , LocalIP , LocalPortNumber , RemoteIP, RemotePortNumber, ReceivedBytes, SentBytes
| sort by LocalIP asc,TimeGenerated asc, RemoteIP asc, RemotePortNumber asc | sort by LocalIP asc,TimeGenerated asc, RemoteIP asc, RemotePortNumber asc
| serialize | serialize
| extend nextTimeGenerated = next(TimeGenerated, 1), nextLocalIP = next(LocalIP, 1) | extend nextTimeGenerated = next(TimeGenerated, 1), nextLocalIP = next(LocalIP, 1)
| extend TimeDeltainSeconds = datetime_diff('second',nextTimeGenerated,TimeGenerated) | extend TimeDeltainSeconds = datetime_diff('second',nextTimeGenerated,TimeGenerated)
| where LocalIP == nextLocalIP | where LocalIP == nextLocalIP
//Whitelisting criteria/ threshold criteria //Whitelisting criteria/ threshold criteria
| where TimeDeltainSeconds > TimeDeltaThreshold | where TimeDeltainSeconds > TimeDeltaThreshold
| where RemotePortNumber != "0" | where RemotePortNumber != "0"
| project TimeGenerated, TimeDeltainSeconds, LocalIP, LocalPortNumber,RemoteIP,RemotePortNumber, ReceivedBytes, SentBytes | project TimeGenerated, TimeDeltainSeconds, LocalIP, LocalPortNumber,RemoteIP,RemotePortNumber, ReceivedBytes, SentBytes
| summarize count(), sum(ReceivedBytes), sum(SentBytes), make_list(TimeDeltainSeconds) by TimeDeltainSeconds, bin(TimeGenerated, 1h), LocalIP, RemoteIP, RemotePortNumber | summarize count(), sum(ReceivedBytes), sum(SentBytes), make_list(TimeDeltainSeconds) by TimeDeltainSeconds, bin(TimeGenerated, 1h), LocalIP, RemoteIP, RemotePortNumber
| summarize (MostFrequentTimeDeltaCount, MostFrequentTimeDeltainSeconds)=arg_max(count_, TimeDeltainSeconds), TotalEvents=sum(count_), TotalSentBytes=sum(sum_SentBytes),TotalReceivedBytes=sum(sum_ReceivedBytes) by bin(TimeGenerated, 1h), LocalIP, RemoteIP, RemotePortNumber | summarize (MostFrequentTimeDeltaCount, MostFrequentTimeDeltainSeconds)=arg_max(count_, TimeDeltainSeconds), TotalEvents=sum(count_), TotalSentBytes=sum(sum_SentBytes),TotalReceivedBytes=sum(sum_ReceivedBytes) by bin(TimeGenerated, 1h), LocalIP, RemoteIP, RemotePortNumber
| where TotalEvents > TotalEventsThreshold | where TotalEvents > TotalEventsThreshold
| extend BeaconPercent = MostFrequentTimeDeltaCount/toreal(TotalEvents) * 100 | extend BeaconPercent = MostFrequentTimeDeltaCount/toreal(TotalEvents) * 100
| where BeaconPercent > PercentBeaconThreshold | where BeaconPercent > PercentBeaconThreshold
| extend timestamp = TimeGenerated, IPCustomEntity = RemoteIP | extend timestamp = TimeGenerated, IPCustomEntity = RemoteIP

Просмотреть файл

@ -9,14 +9,13 @@ relevantTechniques:
- T1078 - T1078
query: | query: |
let hunt_time = 1d;
let previous_tz = ( let previous_tz = (
ZoomLogs ZoomLogs
| where Event =~ "meeting.participant_joined" | where Event =~ "meeting.participant_joined"
| extend TimeZone = columnifexists('payload_object_timezone_s', "") | extend TimeZone = columnifexists('payload_object_timezone_s', "")
| summarize by TimeZone | summarize by TimeZone
); );
ZoomLogs ZoomLogs
| where Event =~ "meeting.participant_joined" | where Event =~ "meeting.participant_joined"
| extend TimeZone = columnifexists('payload_object_timezone_s', "") | extend TimeZone = columnifexists('payload_object_timezone_s', "")
| where isnotempty(TimeZone) and TimeZone in (previous_tz) | where isnotempty(TimeZone) and TimeZone in (previous_tz)