From 8c900dafa215a16394cf302461eb72edf82fa7f3 Mon Sep 17 00:00:00 2001 From: Pete Bryan Date: Fri, 6 Aug 2021 13:39:23 -0700 Subject: [PATCH] Sylog to Zoom --- .../Syslog/RareProcess_ForLxHost.yaml | 21 ++++---- .../Syslog/disabled_account_squid_usage.yaml | 13 ++--- .../Syslog/squid_volume_anomalies.yaml | 17 +++---- .../FileEntity_OfficeActivity.yaml | 5 +- .../FileEntity_SecurityEvent.yaml | 5 +- .../FileEntity_Syslog.yaml | 5 +- .../FileEntity_VMConnection.yaml | 5 +- .../FileEntity_WireData.yaml | 5 +- .../W3CIISLog/WebShellActivity.yaml | 20 ++++---- Hunting Queries/WireData/WireDataBeacon.yaml | 48 +++++++++---------- Hunting Queries/ZoomLogs/NewTZ.yaml | 3 +- 11 files changed, 73 insertions(+), 74 deletions(-) diff --git a/Hunting Queries/Syslog/RareProcess_ForLxHost.yaml b/Hunting Queries/Syslog/RareProcess_ForLxHost.yaml index 3eb4b04716..72b94bdf99 100644 --- a/Hunting Queries/Syslog/RareProcess_ForLxHost.yaml +++ b/Hunting Queries/Syslog/RareProcess_ForLxHost.yaml @@ -1,7 +1,7 @@ id: d0ae35df-0eaf-491f-b23e-8190e4f3ffe9 name: Rare process running on a Linux host 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.' requiredDataConnectors: - connectorId: Syslog @@ -15,26 +15,27 @@ relevantTechniques: - T1053 - T1037 query: | - let starttime = 7d; - let endtime = 1m; - let lookback = 30d; + + let starttime = todatetime('{{StartTimeISO}}'); + let endtime = todatetime('{{EndTimeISO}}'); + let lookback = starttime - 14d; let count_threshold = 100; let perc_threshold = 0.01; let host_threshold = 14; let basic=materialize( Syslog - | where TimeGenerated >= ago(lookback) + | where TimeGenerated >= lookback | summarize FullCount = count() - , Count= countif(TimeGenerated between (ago(starttime) .. ago(endtime))) + , Count= countif(TimeGenerated between (starttime .. endtime)) , min_TimeGenerated=min(TimeGenerated) - , max_TimeGenerated=max(TimeGenerated) + , max_TimeGenerated=max(TimeGenerated) by Computer, ProcessName | where Count > 0 and Count < count_threshold); let basic_avg = basic | summarize Avg = avg(FullCount) by ProcessName; basic | project-away FullCount - | join kind=inner - basic_avg + | join kind=inner + basic_avg 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 diff --git a/Hunting Queries/Syslog/disabled_account_squid_usage.yaml b/Hunting Queries/Syslog/disabled_account_squid_usage.yaml index 927f439804..608ce32473 100644 --- a/Hunting Queries/Syslog/disabled_account_squid_usage.yaml +++ b/Hunting Queries/Syslog/disabled_account_squid_usage.yaml @@ -1,8 +1,8 @@ id: 959fe0f0-7ac0-467c-944f-5b8c6fdc9e72 name: Disabled accounts using Squid proxy description: | - 'Look for accounts that have a been recorded as disabled by AD in the previous week 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/' + '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 time period. This query presumes the default squid log format is being used. http://www.squid-cache.org/Doc/config/access_log/' requiredDataConnectors: - connectorId: Syslog dataTypes: @@ -13,17 +13,18 @@ relevantTechniques: - T1110 query: | - let starttime = 14d; - let endtime = 7d; + let starttime = todatetime('{{StartTimeISO}}'); + let endtime = todatetime('{{EndTimeISO}}'); + let lookback = startime - 14d; let disabledAccounts = (){ SigninLogs - | where TimeGenerated between(ago(starttime) .. ago(endtime)) + | where TimeGenerated between(lookback..starttime)) | where ResultType == 50057 | where ResultDescription =~ "User account is disabled. The account has been disabled by an administrator." }; let proxyEvents = (){ Syslog - | where TimeGenerated > ago(endtime) + | where TimeGenerated between(starttime..endtime) | where ProcessName contains "squid" | 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), diff --git a/Hunting Queries/Syslog/squid_volume_anomalies.yaml b/Hunting Queries/Syslog/squid_volume_anomalies.yaml index f880d6f191..8b64364c98 100644 --- a/Hunting Queries/Syslog/squid_volume_anomalies.yaml +++ b/Hunting Queries/Syslog/squid_volume_anomalies.yaml @@ -2,11 +2,11 @@ id: e472c490-4792-4f12-8b6b-6ab3e0404d35 name: Squid data volume timeseries anomalies description: | '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.' requiredDataConnectors: - connectorId: Syslog - dataTypes: + dataTypes: - Syslog tactics: - CommandAndControl @@ -16,16 +16,12 @@ relevantTechniques: - T1030 query: | - let starttime = 14d; - let endtime = 1d; - let timeframe = 1h; - let TimeSeriesData = + let TimeSeriesData = Syslog - | where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime))) | where ProcessName contains "squid" - | 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), - Status = extract("(TCP_(([A-Z]+)(_[A-Z]+)*)|UDP_(([A-Z]+)(_[A-Z]+)*))",1,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), + 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), User = extract("(CONNECT |GET )([^ ]* )([^ ]+)",3,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 timestamp = TimeGenerated | render timechart with (title="Squid Time Series anomalies") - \ No newline at end of file diff --git a/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_OfficeActivity.yaml b/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_OfficeActivity.yaml index 7c50c64a61..3d845164cd 100644 --- a/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_OfficeActivity.yaml +++ b/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_OfficeActivity.yaml @@ -17,14 +17,15 @@ tactics: - Impact query: | - let dt_lookBack = 1h; + let starttime = todatetime('{{StartTimeISO}}'); + let endtime = todatetime('{{EndTimeISO}}'); let ioc_lookBack = 14d; ThreatIntelligenceIndicator | where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now() | where Active == true | where isnotempty(FileName) | join ( - OfficeActivity| where TimeGenerated >= ago(dt_lookBack) + OfficeActivity| where TimeGenerated between(starttime..endtime) | where isnotempty(SourceFileName) | extend OfficeActivity_TimeGenerated = TimeGenerated ) diff --git a/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_SecurityEvent.yaml b/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_SecurityEvent.yaml index b6e73e85bf..75f35cbf63 100644 --- a/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_SecurityEvent.yaml +++ b/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_SecurityEvent.yaml @@ -17,14 +17,15 @@ tactics: - Impact query: | - let dt_lookBack = 1h; + let starttime = todatetime('{{StartTimeISO}}'); + let endtime = todatetime('{{EndTimeISO}}'); let ioc_lookBack = 14d; ThreatIntelligenceIndicator | where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now() | where Active == true | where isnotempty(FileName) | join ( - SecurityEvent | where TimeGenerated >= ago(dt_lookBack) + SecurityEvent | where TimeGenerated between(starttime..endtime) | where EventID in ("4688","8002","4648","4673") | where isnotempty(Process) | extend SecurityEvent_TimeGenerated = TimeGenerated, Event = EventID diff --git a/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_Syslog.yaml b/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_Syslog.yaml index c33910a476..b38efa4482 100644 --- a/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_Syslog.yaml +++ b/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_Syslog.yaml @@ -17,7 +17,8 @@ tactics: - Impact query: | - let dt_lookBack = 1h; + let starttime = todatetime('{{StartTimeISO}}'); + let endtime = todatetime('{{EndTimeISO}}'); let ioc_lookBack = 14d; ThreatIntelligenceIndicator | where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now() @@ -25,7 +26,7 @@ query: | | where isnotempty(FileName) | extend TI_ProcessEntity = tostring(split(FileName, ".")[-2]) | join ( - Syslog | where TimeGenerated >= ago(dt_lookBack) + Syslog | where TimeGenerated between(starttime..endtime) | where isnotempty(ProcessName) | extend Syslog_TimeGenerated = TimeGenerated ) diff --git a/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_VMConnection.yaml b/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_VMConnection.yaml index e05d2ef094..7a131f5037 100644 --- a/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_VMConnection.yaml +++ b/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_VMConnection.yaml @@ -17,7 +17,8 @@ tactics: - Impact query: | - let dt_lookBack = 1h; + let starttime = todatetime('{{StartTimeISO}}'); + let endtime = todatetime('{{EndTimeISO}}'); let ioc_lookBack = 14d; ThreatIntelligenceIndicator | where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now() @@ -25,7 +26,7 @@ query: | | where isnotempty(FileName) | extend TI_ProcessEntity = tostring(split(FileName, ".")[-2]) | join ( - VMConnection | where TimeGenerated >= ago(dt_lookBack) + VMConnection | where TimeGenerated between(starttime..endtime) | where isnotempty(ProcessName) | extend VMConnection_TimeGenerated = TimeGenerated ) diff --git a/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_WireData.yaml b/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_WireData.yaml index 38c55bc330..abaa0d3044 100644 --- a/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_WireData.yaml +++ b/Hunting Queries/ThreatIntelligenceIndicator/FileEntity_WireData.yaml @@ -17,14 +17,15 @@ tactics: - Impact query: | - let dt_lookBack = 1h; + let starttime = todatetime('{{StartTimeISO}}'); + let endtime = todatetime('{{EndTimeISO}}'); let ioc_lookBack = 14d; ThreatIntelligenceIndicator | where TimeGenerated >= ago(ioc_lookBack) and ExpirationDateTime > now() | where Active == true | where isnotempty(FileName) | join ( - WireData | where TimeGenerated >= ago(dt_lookBack) + WireData | where TimeGenerated between(starttime..endtime) | where isnotempty(ProcessName) | extend Process =reverse(substring(reverse(ProcessName), 0, indexof(reverse(ProcessName), "\\"))) | extend WireData_TimeGenerated = TimeGenerated diff --git a/Hunting Queries/W3CIISLog/WebShellActivity.yaml b/Hunting Queries/W3CIISLog/WebShellActivity.yaml index 52557eb130..ea44f180bf 100644 --- a/Hunting Queries/W3CIISLog/WebShellActivity.yaml +++ b/Hunting Queries/W3CIISLog/WebShellActivity.yaml @@ -2,8 +2,8 @@ id: e0c947c3-fe83-46ff-bbda-a43224a785fd name: Web Shell Activity description: | '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. The results obtained summarise the public client IPs, user agents, and the distribution of the above scripts between the start and end time.' requiredDataConnectors: @@ -15,25 +15,25 @@ tactics: - InitialAccess relevantTechniques: - T1100 -query: | +query: | - let end_time = now(); - let start_time = end_time - timespan(1d); - let lookback_time_from_start_time = timespan(3d); + let starttime = todatetime('{{StartTimeISO}}'); + let endtime = todatetime('{{EndTimeISO}}'); + 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 ignore_uristems = dynamic(["/ews/exchange.asmx"]); let lookback_period = ( - W3CIISLog - | where TimeGenerated between ((start_time - lookback_time_from_start_time).. start_time) + W3CIISLog + | where TimeGenerated between (lookback .. starttime) | where not(ipv4_is_private(cIP)) and cIP != "127.0.0.1" | summarize count() by cIP, csUserAgent | project cIP, csUserAgent ); let potential_webshell_activity = (W3CIISLog - | where TimeGenerated between (start_time .. end_time) + | where TimeGenerated between (starttime .. endtime) | extend csUriStem = tolower(csUriStem) | 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 is_script = iff(suffix in (script_extensions), 1, 0) | where not(ipv4_is_private(cIP)) and cIP != "127.0.0.1" diff --git a/Hunting Queries/WireData/WireDataBeacon.yaml b/Hunting Queries/WireData/WireDataBeacon.yaml index 90394d7765..28aa9625d9 100644 --- a/Hunting Queries/WireData/WireDataBeacon.yaml +++ b/Hunting Queries/WireData/WireDataBeacon.yaml @@ -2,10 +2,10 @@ id: 33aa0e01-87e2-43ea-87f9-2f7e3ff1d532 name: Detect beacon like pattern based on repetitive time intervals in Wire Data Traffic description: | '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. - References: Blog about creating dataset to identify network beaconing via repetitive time intervals seen against total traffic - between same source-destination pair. + References: Blog about creating dataset to identify network beaconing via repetitive time intervals seen against total traffic + 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/' requiredDataConnectors: - connectorId: AzureMonitor(WireData) @@ -18,30 +18,28 @@ relevantTechniques: - T1065 query: | - let starttime = 7d; - let endtime = 1d; - let TimeDeltaThreshold = 10; - let TotalEventsThreshold = 15; - let PercentBeaconThreshold = 95; - let PrivateIPregex = @'^127\.|^10\.|^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[0-1]\.|^192\.168\.'; + let lookback = 1d; + let TimeDeltaThreshold = 10; + let TotalEventsThreshold = 15; + let PercentBeaconThreshold = 95; + let PrivateIPregex = @'^127\.|^10\.|^172\.1[6-9]\.|^172\.2[0-9]\.|^172\.3[0-1]\.|^192\.168\.'; WireData - | where TimeGenerated between (ago(starttime)..ago(endtime)) - | extend RemoteIPType = iff(RemoteIP matches regex PrivateIPregex,"private" ,"public" ) - | where RemoteIPType =="public" - | project TimeGenerated , LocalIP , LocalPortNumber , RemoteIP, RemotePortNumber, ReceivedBytes, SentBytes - | sort by LocalIP asc,TimeGenerated asc, RemoteIP asc, RemotePortNumber asc + | where TimeGenerated > lookback + | extend RemoteIPType = iff(RemoteIP matches regex PrivateIPregex,"private" ,"public" ) + | where RemoteIPType =="public" + | project TimeGenerated , LocalIP , LocalPortNumber , RemoteIP, RemotePortNumber, ReceivedBytes, SentBytes + | sort by LocalIP asc,TimeGenerated asc, RemoteIP asc, RemotePortNumber asc | serialize - | extend nextTimeGenerated = next(TimeGenerated, 1), nextLocalIP = next(LocalIP, 1) - | extend TimeDeltainSeconds = datetime_diff('second',nextTimeGenerated,TimeGenerated) - | where LocalIP == nextLocalIP - //Whitelisting criteria/ threshold criteria - | where TimeDeltainSeconds > TimeDeltaThreshold + | extend nextTimeGenerated = next(TimeGenerated, 1), nextLocalIP = next(LocalIP, 1) + | extend TimeDeltainSeconds = datetime_diff('second',nextTimeGenerated,TimeGenerated) + | where LocalIP == nextLocalIP + //Whitelisting criteria/ threshold criteria + | where TimeDeltainSeconds > TimeDeltaThreshold | where RemotePortNumber != "0" - | 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 (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 - | extend BeaconPercent = MostFrequentTimeDeltaCount/toreal(TotalEvents) * 100 + | 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 (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 + | extend BeaconPercent = MostFrequentTimeDeltaCount/toreal(TotalEvents) * 100 | where BeaconPercent > PercentBeaconThreshold | extend timestamp = TimeGenerated, IPCustomEntity = RemoteIP - \ No newline at end of file diff --git a/Hunting Queries/ZoomLogs/NewTZ.yaml b/Hunting Queries/ZoomLogs/NewTZ.yaml index 480f1deabe..004815f9b6 100644 --- a/Hunting Queries/ZoomLogs/NewTZ.yaml +++ b/Hunting Queries/ZoomLogs/NewTZ.yaml @@ -9,14 +9,13 @@ relevantTechniques: - T1078 query: | - let hunt_time = 1d; let previous_tz = ( ZoomLogs | where Event =~ "meeting.participant_joined" | extend TimeZone = columnifexists('payload_object_timezone_s', "") | summarize by TimeZone ); - ZoomLogs + ZoomLogs | where Event =~ "meeting.participant_joined" | extend TimeZone = columnifexists('payload_object_timezone_s', "") | where isnotempty(TimeZone) and TimeZone in (previous_tz)