From 622a7177f69a29e0e4c337e3f84ba881ca72495a Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Wed, 2 Oct 2024 09:09:05 +0300 Subject: [PATCH 01/27] Fixed Analytical rules --- ... to Team and immediately uploads file.yaml | 56 ++++++++++-- ...365 - ExternalUserAddedRemovedInTeams.yaml | 60 ++++++++----- ... Mail_redirect_via_ExO_transport_rule.yaml | 62 ++++++++----- .../Office 365 - Malicious_Inbox_Rule.yaml | 41 +++++++-- .../Office 365 - MultipleTeamsDeletes.yaml | 34 ++++++-- .../Office 365 - Office_MailForwarding.yaml | 86 +++++++++++++------ ...ice 365 - Office_Uploaded_Executables.yaml | 86 +++++++++++++------ .../Office 365 - RareOfficeOperations.yaml | 28 ++++-- ...ce 365 - SharePoint_Downloads_byNewIP.yaml | 76 +++++++++------- ...- SharePoint_Downloads_byNewUserAgent.yaml | 74 ++++++++++------ ...ffice 365 - exchange_auditlogdisabled.yaml | 47 +++++++--- .../Office 365 - office_policytampering.yaml | 73 +++++++++++----- ...repoint_file_transfer_above_threshold.yaml | 37 ++++++-- ...file_transfer_folders_above_threshold.yaml | 43 +++++++--- 14 files changed, 570 insertions(+), 233 deletions(-) diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - External User added to Team and immediately uploads file.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - External User added to Team and immediately uploads file.yaml index ae3352d686..2dc1409234 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - External User added to Team and immediately uploads file.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - External User added to Team and immediately uploads file.yaml @@ -1,5 +1,5 @@ id: 4d38f80f-6b7d-4a1f-aeaf-e38df637e6ac -name: Office 365 - Accessed files shared by temporary external user +name: GSA Enriched Office 365 - Accessed files shared by temporary external user description: | 'This detection identifies when an external user is added to a Team or Teams chat and shares a file which is accessed by many users (>10) and the users is removed within short period of time. This might be an indicator of suspicious activity.' severity: Low @@ -17,8 +17,41 @@ tactics: relevantTechniques: - T1566 query: | - let fileAccessThreshold = 10; - EnrichedMicrosoft365AuditLogs + let fileAccessThreshold = 10; + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where OfficeWorkload =~ "MicrosoftTeams" + | where Operation =~ "MemberAdded" + | extend MemberAdded = tostring(parse_json(Members)[0].UPN) + | where MemberAdded contains "#EXT#" + | project TimeAdded = TimeGenerated, Operation, MemberAdded, UserWhoAdded = UserId, TeamName + | join kind=inner ( + OfficeActivity + | where OfficeWorkload =~ "MicrosoftTeams" + | where Operation =~ "MemberRemoved" + | extend MemberAdded = tostring(parse_json(Members)[0].UPN) + | where MemberAdded contains "#EXT#" + | project TimeDeleted = TimeGenerated, Operation, MemberAdded, UserWhoDeleted = UserId, TeamName + ) on MemberAdded + | where TimeDeleted > TimeAdded + | join kind=inner ( + OfficeActivity + | where RecordType == "SharePointFileOperation" + | where SourceRelativeUrl has "Microsoft Teams Chat Files" + | where Operation == "FileUploaded" + | extend MemberAdded = UserId + | join kind=inner ( + OfficeActivity + | where RecordType == "SharePointFileOperation" + | where Operation == "FileAccessed" + | where SourceRelativeUrl has "Microsoft Teams Chat Files" + | summarize FileAccessCount = count() by OfficeObjectId + | where FileAccessCount > fileAccessThreshold + ) on OfficeObjectId + ) on MemberAdded + | project TimeAdded, TimeDeleted, MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName; + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs | where Workload =~ "MicrosoftTeams" | where Operation =~ "MemberAdded" | extend MemberAdded = tostring(parse_json(tostring(AdditionalProperties)).Members[0].UPN) @@ -49,10 +82,17 @@ query: | | where FileAccessCount > fileAccessThreshold ) on ObjectId, TeamName ) on MemberAdded, TeamName - | project-away MemberAdded1, MemberAdded2, ObjectId1, Operation1, Operation2 - | extend MemberAddedAccountName = tostring(split(MemberAdded, "@")[0]), MemberAddedAccountUPNSuffix = tostring(split(MemberAdded, "@")[1]) - | extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, "@")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, "@")[1]) - | extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, "@")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, "@")[1]) + | project TimeAdded, TimeDeleted, MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName; + // Combine Office and Enriched Events and Deduplicate + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(TimeAdded, *) by MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName; + // Project Final Output + CombinedEvents + | extend MemberAddedAccountName = tostring(split(MemberAdded, "@")[0]), MemberAddedAccountUPNSuffix = tostring(split(MemberAdded, "@")[1]) + | extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, "@")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, "@")[1]) + | extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, "@")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, "@")[1]) + | project TimeAdded, TimeDeleted, MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName, MemberAddedAccountName, MemberAddedAccountUPNSuffix, UserWhoAddedAccountName, UserWhoAddedAccountUPNSuffix, UserWhoDeletedAccountName, UserWhoDeletedAccountUPNSuffix entityMappings: - entityType: Account fieldMappings: @@ -82,5 +122,5 @@ entityMappings: fieldMappings: - identifier: Address columnName: ClientIP -version: 2.1.2 +version: 2.1.3 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - ExternalUserAddedRemovedInTeams.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - ExternalUserAddedRemovedInTeams.yaml index ee67b9c088..b7d16137d2 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - ExternalUserAddedRemovedInTeams.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - ExternalUserAddedRemovedInTeams.yaml @@ -1,5 +1,5 @@ id: 1a8f1297-23a4-4f09-a20b-90af8fc3641a -name: Office 365 - External User Added and Removed in Short Timeframe +name: GSA Enriched Office 365 - External User Added and Removed in Short Timeframe description: | This detection flags the occurrences of external user accounts that are added to a Team and then removed within one hour. severity: Low @@ -17,27 +17,47 @@ tactics: relevantTechniques: - T1136 query: | - let TeamsAddDel = (Op:string){ + let TeamsAddDelOffice = (Op:string){ + OfficeActivity + | where OfficeWorkload =~ "MicrosoftTeams" + | where Operation == Op + | where Members has ("#EXT#") + | mv-expand Members + | extend UPN = tostring(Members.UPN) + | where UPN has ("#EXT#") + | project TimeGenerated, Operation, UPN, UserId, TeamName, ClientIP + }; + let TeamsAddDelEnriched = (Op:string){ EnrichedMicrosoft365AuditLogs - | where Workload =~ "MicrosoftTeams" - | where Operation == Op - | where tostring(AdditionalProperties.Members) has ("#EXT#") - | mv-expand Members = parse_json(tostring(AdditionalProperties.Members)) - | extend UPN = tostring(Members.UPN) - | where UPN has ("#EXT#") - | project TimeGenerated, Operation, UPN, UserId, TeamName = tostring(AdditionalProperties.TeamName), ClientIP = SourceIp - }; - let TeamsAdd = TeamsAddDel("MemberAdded") + | where Workload =~ "MicrosoftTeams" + | where Operation == Op + | where tostring(AdditionalProperties.Members) has ("#EXT#") + | mv-expand Members = parse_json(tostring(AdditionalProperties.Members)) + | extend UPN = tostring(Members.UPN) + | where UPN has ("#EXT#") + | project TimeGenerated, Operation, UPN, UserId, TeamName = tostring(AdditionalProperties.TeamName), ClientIP = SourceIp + }; + let TeamsAddOffice = TeamsAddDelOffice("MemberAdded") | project TimeAdded = TimeGenerated, Operation, MemberAdded = UPN, UserWhoAdded = UserId, TeamName, ClientIP; - let TeamsDel = TeamsAddDel("MemberRemoved") + let TeamsDelOffice = TeamsAddDelOffice("MemberRemoved") | project TimeDeleted = TimeGenerated, Operation, MemberRemoved = UPN, UserWhoDeleted = UserId, TeamName, ClientIP; - TeamsAdd - | join kind = inner (TeamsDel) on $left.MemberAdded == $right.MemberRemoved - | where TimeDeleted > TimeAdded - | project TimeAdded, TimeDeleted, MemberAdded_Removed = MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName - | extend MemberAdded_RemovedAccountName = tostring(split(MemberAdded_Removed, "@")[0]), MemberAddedAccountUPNSuffix = tostring(split(MemberAdded_Removed, "@")[1]) - | extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, "@")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, "@")[1]) - | extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, "@")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, "@")[1]) + let TeamsAddEnriched = TeamsAddDelEnriched("MemberAdded") + | project TimeAdded = TimeGenerated, Operation, MemberAdded = UPN, UserWhoAdded = UserId, TeamName, ClientIP; + let TeamsDelEnriched = TeamsAddDelEnriched("MemberRemoved") + | project TimeDeleted = TimeGenerated, Operation, MemberRemoved = UPN, UserWhoDeleted = UserId, TeamName, ClientIP; + let TeamsAdd = TeamsAddOffice + | union TeamsAddEnriched + | project TimeAdded, Operation, MemberAdded, UserWhoAdded, TeamName, ClientIP; + let TeamsDel = TeamsDelOffice + | union TeamsDelEnriched + | project TimeDeleted, Operation, MemberRemoved, UserWhoDeleted, TeamName, ClientIP; + TeamsAdd + | join kind=inner (TeamsDel) on $left.MemberAdded == $right.MemberRemoved + | where TimeDeleted > TimeAdded + | project TimeAdded, TimeDeleted, MemberAdded_Removed = MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName, ClientIP + | extend MemberAdded_RemovedAccountName = tostring(split(MemberAdded_Removed, "@")[0]), MemberAdded_RemovedAccountUPNSuffix = tostring(split(MemberAdded_Removed, "@")[1]) + | extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, "@")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, "@")[1]) + | extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, "@")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, "@")[1]) entityMappings: - entityType: Account fieldMappings: @@ -67,5 +87,5 @@ entityMappings: fieldMappings: - identifier: Address columnName: ClientIp -version: 2.1.3 +version: 2.1.4 kind: Scheduled \ No newline at end of file diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml index b4e3a1357d..9c86a9f4da 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml @@ -1,5 +1,5 @@ id: edcfc2e0-3134-434c-8074-9101c530d419 -name: Office 365 - Mail redirect via ExO transport rule +name: GSA Enriched Office 365 - Mail redirect via ExO transport rule description: | 'Identifies when Exchange Online transport rule configured to forward emails. This could be an adversary mailbox configured to collect mail from multiple user accounts.' @@ -20,24 +20,46 @@ relevantTechniques: - T1114 - T1020 query: | - EnrichedMicrosoft365AuditLogs - | where Workload == "Exchange" - | where Operation in~ ("New-TransportRule", "Set-TransportRule") - | mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on ( - summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) - ) - | extend RuleName = case( - Operation =~ "Set-TransportRule", ObjectId, // Assuming ObjectId maps to what was previously OfficeObjectId - Operation =~ "New-TransportRule", ParsedParameters.Name, - "Unknown" - ) - | mv-expand ExpandedParameters = todynamic(tostring(AdditionalProperties.Parameters)) - | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) - | extend RedirectTo = ExpandedParameters.Value - | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIp)[0] - | extend From = ParsedParameters.From - | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters = tostring(AdditionalProperties.Parameters) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + let officeActivityQuery = OfficeActivity + | where OfficeWorkload == "Exchange" + | where Operation in~ ("New-TransportRule", "Set-TransportRule") + | mv-apply DynamicParameters = todynamic(Parameters) on (summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))) + | extend RuleName = case( + Operation =~ "Set-TransportRule", OfficeObjectId, + Operation =~ "New-TransportRule", ParsedParameters.Name, + "Unknown") + | mv-expand ExpandedParameters = todynamic(Parameters) + | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) + | extend RedirectTo = ExpandedParameters.Value + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] + | extend From = ParsedParameters.From + | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + + let enrichedLogsQuery = EnrichedMicrosoft365AuditLogs + | where Workload == "Exchange" + | where Operation in~ ("New-TransportRule", "Set-TransportRule") + | mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on ( + summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) + ) + | extend RuleName = case( + Operation =~ "Set-TransportRule", ObjectId, // Assuming ObjectId maps to OfficeObjectId + Operation =~ "New-TransportRule", ParsedParameters.Name, + "Unknown" + ) + | mv-expand ExpandedParameters = todynamic(tostring(AdditionalProperties.Parameters)) + | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) + | extend RedirectTo = ExpandedParameters.Value + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIp)[0] + | extend From = ParsedParameters.From + | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters = tostring(AdditionalProperties.Parameters) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + + // Combine both queries + union isfuzzy=true officeActivityQuery, enrichedLogsQuery + | summarize arg_min(TimeGenerated, *) by RuleName, RedirectTo + | project TimeGenerated, RedirectTo, IPAddress, Port, UserId, From, Operation, RuleName, Parameters, AccountName, AccountUPNSuffix + | order by TimeGenerated desc; entityMappings: - entityType: Account fieldMappings: @@ -51,5 +73,5 @@ entityMappings: fieldMappings: - identifier: Address columnName: IPAddress -version: 2.0.5 +version: 2.0.7 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Malicious_Inbox_Rule.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Malicious_Inbox_Rule.yaml index 7f9c6b7ec7..efdb71967e 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Malicious_Inbox_Rule.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Malicious_Inbox_Rule.yaml @@ -1,5 +1,5 @@ id: a9c76c8d-f60d-49ec-9b1f-bdfee6db3807 -name: Office 365 - Malicious Inbox Rule +name: GSA Enriched Office 365 - Malicious Inbox Rule description: | 'Often times after the initial compromise the attackers create inbox rules to delete emails that contain certain keywords. This is done so as to limit ability to warn compromised users that they've been compromised. Below is a sample query that tries to detect this. @@ -21,20 +21,45 @@ relevantTechniques: - T1098 - T1078 query: | - let Keywords = dynamic(["helpdesk", "alert", "suspicious", "fake", "malicious", "phishing", "spam", "do not click", "do not open", "hijacked", "Fatal"]); - EnrichedMicrosoft365AuditLogs + let Keywords = dynamic(["helpdesk", "alert", "suspicious", "fake", "malicious", "phishing", "spam", "do not click", "do not open", "hijacked", "Fatal"]); + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where OfficeWorkload =~ "Exchange" + | where Operation =~ "New-InboxRule" and (ResultStatus =~ "True" or ResultStatus =~ "Succeeded") + | where Parameters has "Deleted Items" or Parameters has "Junk Email" or Parameters has "DeleteMessage" + | extend Events = todynamic(Parameters) + | parse Events with * "SubjectContainsWords" SubjectContainsWords '}'* + | parse Events with * "BodyContainsWords" BodyContainsWords '}'* + | parse Events with * "SubjectOrBodyContainsWords" SubjectOrBodyContainsWords '}'* + | where SubjectContainsWords has_any (Keywords) + or BodyContainsWords has_any (Keywords) + or SubjectOrBodyContainsWords has_any (Keywords) + | extend ClientIPAddress = case(ClientIP has ".", tostring(split(ClientIP, ":")[0]), ClientIP has "[", tostring(trim_start(@'[[]', tostring(split(ClientIP, "]")[0]))), ClientIP) + | extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords)) + | extend RuleDetail = case(OfficeObjectId contains '/', tostring(split(OfficeObjectId, '/')[-1]), tostring(split(OfficeObjectId, '\\')[-1])) + | summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Operation, UserId, ClientIPAddress, ResultStatus, Keyword, OriginatingServer, OfficeObjectId, RuleDetail + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend OriginatingServerName = tostring(split(OriginatingServer, " ")[0]); + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs | where Workload =~ "Exchange" | where Operation =~ "New-InboxRule" and (ResultStatus =~ "True" or ResultStatus =~ "Succeeded") | where tostring(parse_json(tostring(AdditionalProperties)).Parameters) has "Deleted Items" or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has "Junk Email" or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has "DeleteMessage" | extend Events = parse_json(tostring(AdditionalProperties)).Parameters | extend SubjectContainsWords = tostring(Events.SubjectContainsWords), BodyContainsWords = tostring(Events.BodyContainsWords), SubjectOrBodyContainsWords = tostring(Events.SubjectOrBodyContainsWords) | where SubjectContainsWords has_any (Keywords) or BodyContainsWords has_any (Keywords) or SubjectOrBodyContainsWords has_any (Keywords) - | extend ClientIPAddress = case(ClientIp has ".", tostring(split(ClientIp, ":")[0]), ClientIp has "[", tostring(trim_start(@'[[]',tostring(split(ClientIp, "]")[0]))), ClientIp) - | extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, (iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords))) + | extend ClientIPAddress = case(ClientIp has ".", tostring(split(ClientIp, ":")[0]), ClientIp has "[", tostring(trim_start(@'[[]', tostring(split(ClientIp, "]")[0]))), ClientIp) + | extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords)) | extend RuleDetail = case(ObjectId contains '/', tostring(split(ObjectId, '/')[-1]), tostring(split(ObjectId, '\\')[-1])) | summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Operation, UserId, ClientIPAddress, ResultStatus, Keyword, ObjectId, RuleDetail - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) - + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine and Deduplicate + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(StartTimeUtc, *) by Operation, UserId, ClientIPAddress; + // Final Output + CombinedEvents + | project StartTimeUtc, EndTimeUtc, Operation, UserId, ClientIPAddress, ResultStatus, Keyword, RuleDetail, AccountName, AccountUPNSuffix; entityMappings: - entityType: Account fieldMappings: @@ -52,5 +77,5 @@ entityMappings: fieldMappings: - identifier: Address columnName: ClientIPAddress -version: 2.0.5 +version: 2.0.6 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - MultipleTeamsDeletes.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - MultipleTeamsDeletes.yaml index aa8d9a37b4..c5992b559f 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - MultipleTeamsDeletes.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - MultipleTeamsDeletes.yaml @@ -1,5 +1,5 @@ id: db60e4b6-a845-4f28-a18c-94ebbaad6c6c -name: Office 365 - Multiple Teams deleted by a single user +name: GSA Enriched Office 365 - Multiple Teams deleted by a single user description: | 'This detection flags the occurrences of deleting multiple teams within an hour. This data is a part of Office 365 Connector in Microsoft Sentinel.' @@ -19,13 +19,29 @@ relevantTechniques: - T1485 - T1489 query: | - let max_delete_count = 3; - EnrichedMicrosoft365AuditLogs - | where Workload =~ "MicrosoftTeams" - | where Operation =~ "TeamDeleted" - | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DeletedTeams = make_set(tostring(AdditionalProperties.TeamName), 1000) by UserId - | where array_length(DeletedTeams) > max_delete_count - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + // Set the maximum number of deleted teams to flag suspicious activity + let max_delete_count = 3; + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload =~ "MicrosoftTeams" + | where Operation =~ "TeamDeleted" + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DeletedTeams = make_set(tostring(AdditionalProperties.TeamName), 1000) by UserId + | where array_length(DeletedTeams) > max_delete_count + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where OfficeWorkload =~ "MicrosoftTeams" + | where Operation =~ "TeamDeleted" + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DeletedTeams = make_set(TeamName, 1000) by UserId + | where array_length(DeletedTeams) > max_delete_count + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine and Deduplicate Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(StartTime, *) by UserId; + // Final Output + CombinedEvents + | project StartTime, EndTime, DeletedTeams, UserId, AccountName, AccountUPNSuffix entityMappings: - entityType: Account fieldMappings: @@ -35,5 +51,5 @@ entityMappings: columnName: AccountName - identifier: UPNSuffix columnName: AccountUPNSuffix -version: 2.0.5 +version: 2.0.6 kind: Scheduled \ No newline at end of file diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_MailForwarding.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_MailForwarding.yaml index f68c7dc7d3..d56fff5a6f 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_MailForwarding.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_MailForwarding.yaml @@ -1,5 +1,5 @@ id: d75e8289-d1cb-44d4-bd59-2f44a9172478 -name: Office 365 - Multiple Users Email Forwarded to Same Destination +name: GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination description: | Identifies when multiple (more than one) users' mailboxes are configured to forward to the same destination. This could be an attacker-controlled destination mailbox configured to collect mail from multiple compromised user accounts. @@ -20,30 +20,64 @@ relevantTechniques: - T1114 - T1020 query: | - let queryfrequency = 1d; - let queryperiod = 7d; - EnrichedMicrosoft365AuditLogs - | where TimeGenerated > ago(queryperiod) - | where Workload =~ "Exchange" - //| where Operation in ("Set-Mailbox", "New-InboxRule", "Set-InboxRule") // Uncomment or adjust based on actual field usage - | where tostring(AdditionalProperties.Parameters) has_any ("ForwardTo", "RedirectTo", "ForwardingSmtpAddress") - | mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on ( - summarize ParsedParameters = make_bag(bag_pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) - ) - | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source') - | extend DestinationMailAddress = tolower(case( - isnotempty(column_ifexists("ForwardTo", "")), column_ifexists("ForwardTo", ""), - isnotempty(column_ifexists("RedirectTo", "")), column_ifexists("RedirectTo", ""), - isnotempty(column_ifexists("ForwardingSmtpAddress", "")), trim_start(@"smtp:", column_ifexists("ForwardingSmtpAddress", "")), - "")) - | where isnotempty(DestinationMailAddress) - | mv-expand split(DestinationMailAddress, ";") - | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIp)[0] - | extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]) - | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP - | where DistinctUserCount > 1 and EndTime > ago(queryfrequency) - | mv-expand UserId to typeof(string) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + // Set query parameters + let queryfrequency = 1d; + let queryperiod = 7d; + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where TimeGenerated > ago(queryperiod) + | where Workload =~ "Exchange" + // Uncomment or adjust the following line based on actual field usage + // | where Operation in ("Set-Mailbox", "New-InboxRule", "Set-InboxRule") + | where tostring(AdditionalProperties.Parameters) has_any ("ForwardTo", "RedirectTo", "ForwardingSmtpAddress") + | mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on ( + summarize ParsedParameters = make_bag(bag_pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) + ) + | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source') + | extend DestinationMailAddress = tolower(case( + isnotempty(column_ifexists("ForwardTo", "")), column_ifexists("ForwardTo", ""), + isnotempty(column_ifexists("RedirectTo", "")), column_ifexists("RedirectTo", ""), + isnotempty(column_ifexists("ForwardingSmtpAddress", "")), trim_start(@"smtp:", column_ifexists("ForwardingSmtpAddress", "")), + "")) + | where isnotempty(DestinationMailAddress) + | mv-expand split(DestinationMailAddress, ";") + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIp)[0] + | extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]) + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP + | where DistinctUserCount > 1 and EndTime > ago(queryfrequency) + | mv-expand UserId to typeof(string) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where TimeGenerated > ago(queryperiod) + | where OfficeWorkload =~ "Exchange" + // Uncomment or adjust the following line based on actual field usage + // | where Operation in ("Set-Mailbox", "New-InboxRule", "Set-InboxRule") + | where Parameters has_any ("ForwardTo", "RedirectTo", "ForwardingSmtpAddress") + | mv-apply DynamicParameters = todynamic(Parameters) on ( + summarize ParsedParameters = make_bag(bag_pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) + ) + | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source') + | extend DestinationMailAddress = tolower(case( + isnotempty(column_ifexists("ForwardTo", "")), column_ifexists("ForwardTo", ""), + isnotempty(column_ifexists("RedirectTo", "")), column_ifexists("RedirectTo", ""), + isnotempty(column_ifexists("ForwardingSmtpAddress", "")), trim_start(@"smtp:", column_ifexists("ForwardingSmtpAddress", "")), + "")) + | where isnotempty(DestinationMailAddress) + | mv-expand split(DestinationMailAddress, ";") + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] + | extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]) + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP + | where DistinctUserCount > 1 and EndTime > ago(queryfrequency) + | mv-expand UserId to typeof(string) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine and Deduplicate Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(StartTime, *) by tostring(DestinationMailAddress), ClientIP; + // Final Output + CombinedEvents + | project StartTime, EndTime, DestinationMailAddress, ClientIP, DistinctUserCount, UserId, Ports, EventCount, AccountName, AccountUPNSuffix entityMappings: - entityType: Account fieldMappings: @@ -57,5 +91,5 @@ entityMappings: fieldMappings: - identifier: Address columnName: ClientIP -version: 2.0.4 +version: 2.0.5 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_Uploaded_Executables.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_Uploaded_Executables.yaml index 81fcfb8ad1..ecb8cc0365 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_Uploaded_Executables.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_Uploaded_Executables.yaml @@ -1,5 +1,5 @@ id: 178c62b4-d5e5-40f5-8eab-7fccd0051e7a -name: Office 365 - New Executable via Office FileUploaded Operation +name: GSA Enriched Office 365 - New Executable via Office FileUploaded Operation description: | Identifies when executable file types are uploaded to Office services such as SharePoint and OneDrive. List currently includes exe, inf, gzip, cmd, bat file extensions. @@ -24,37 +24,71 @@ relevantTechniques: query: | let threshold = 2; let uploadOp = 'FileUploaded'; + // Extensions that are interesting. Add/Remove to this list as you see fit let execExt = dynamic(['exe', 'inf', 'gzip', 'cmd', 'bat']); let starttime = 8d; let endtime = 1d; - EnrichedMicrosoft365AuditLogs - | where TimeGenerated >= ago(endtime) - | where Operation == uploadOp - | extend SourceFileExtension = extract(@"\.([^\./]+)$", 1, tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)) // Extract file extension - | where SourceFileExtension in (execExt) - | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) - | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl) - | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName) - | project TimeGenerated, Id, Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent), Site_Url, SourceRelativeUrl, SourceFileName - | join kind=leftanti ( - EnrichedMicrosoft365AuditLogs - | where TimeGenerated between (ago(starttime) .. ago(endtime)) + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where TimeGenerated >= ago(endtime) + | where Operation =~ uploadOp + | where SourceFileExtension has_any (execExt) + | extend RecordType = coalesce(tostring(RecordType), "UnknownRecordType") // Ensure RecordType is a string + | project TimeGenerated, OfficeId, OfficeWorkload = OfficeWorkload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, UserAgent, Site_Url, SourceRelativeUrl, SourceFileName + | join kind=leftanti ( + OfficeActivity + | where TimeGenerated between (ago(starttime) .. ago(endtime)) + | where Operation =~ uploadOp + | where SourceFileExtension has_any (execExt) + | summarize SourceRelativeUrl = make_set(SourceRelativeUrl, 100000), UserId = make_set(UserId, 100000), PrevSeenCount = count() by SourceFileName + // Uncomment the line below to enforce the threshold + //| where PrevSeenCount > threshold + | mvexpand SourceRelativeUrl, UserId + | extend SourceRelativeUrl = tostring(SourceRelativeUrl), UserId = tostring(UserId) // Ensure consistent types for SourceRelativeUrl and UserId + ) on SourceFileName, SourceRelativeUrl, UserId + | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2]) + | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\.', '_')) + | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false) + | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), UserAgents = make_list(UserAgent, 100000), OfficeIds = make_list(OfficeId, 100000), SourceRelativeUrls = make_list(SourceRelativeUrl, 100000), FileNames = make_list(tostring(SourceFileName), 100000) // Cast FileNames to string + by OfficeWorkload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where TimeGenerated >= ago(endtime) | where Operation == uploadOp | extend SourceFileExtension = extract(@"\.([^\./]+)$", 1, tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)) // Extract file extension | where SourceFileExtension in (execExt) + | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl) - | summarize SourceRelativeUrl = make_set(SourceRelativeUrl, 100000), UserId = make_set(UserId, 100000), PrevSeenCount = count() by SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName) - // Uncomment the line below to enforce the threshold - // | where PrevSeenCount > threshold - | mvexpand SourceRelativeUrl, UserId - | extend SourceRelativeUrl = tostring(SourceRelativeUrl), UserId = tostring(UserId) - ) on SourceFileName, SourceRelativeUrl, UserId - | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2]) - | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\.', '_')) - | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false) - | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), UserAgents = make_list(UserAgent, 100000), Ids = make_list(Id, 100000), SourceRelativeUrls = make_list(SourceRelativeUrl, 100000), FileNames = make_list(SourceFileName, 100000) - by Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName) + | extend RecordType = coalesce(tostring(RecordType), "UnknownRecordType") // Ensure RecordType is a string + | project TimeGenerated, Id, Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent), Site_Url, SourceRelativeUrl, SourceFileName + | join kind=leftanti ( + EnrichedMicrosoft365AuditLogs + | where TimeGenerated between (ago(starttime) .. ago(endtime)) + | where Operation == uploadOp + | extend SourceFileExtension = extract(@"\.([^\./]+)$", 1, tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)) // Extract file extension + | where SourceFileExtension in (execExt) + | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl) + | summarize SourceRelativeUrl = make_set(SourceRelativeUrl, 100000), UserId = make_set(UserId, 100000), PrevSeenCount = count() by SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName) + // Uncomment the line below to enforce the threshold + //| where PrevSeenCount > threshold + | mvexpand SourceRelativeUrl, UserId + | extend SourceRelativeUrl = tostring(SourceRelativeUrl), UserId = tostring(UserId) // Ensure consistent types for SourceRelativeUrl and UserId + ) on SourceFileName, SourceRelativeUrl, UserId + | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2]) + | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\.', '_')) + | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false) + | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), UserAgents = make_list(UserAgent, 100000), Ids = make_list(Id, 100000), SourceRelativeUrls = make_list(tostring(SourceRelativeUrl), 100000), FileNames = make_list(tostring(SourceFileName), 100000) // Cast FileNames to string + by Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine Office and Enriched Logs + let CombinedEvents = EnrichedEvents + | union isfuzzy=true OfficeEvents + | summarize arg_min(StartTime, *) by tostring(FileNames), UserId, Site_Url, tostring(RecordType) // Ensure FileNames and RecordType are strings + | order by StartTime desc; + CombinedEvents + | project StartTime, EndTime, Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder, FileNames, UserAgents, AccountName, AccountUPNSuffix; entityMappings: - entityType: Account fieldMappings: @@ -76,5 +110,5 @@ entityMappings: fieldMappings: - identifier: Name columnName: FileNames -version: 2.0.6 +version: 2.0.7 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - RareOfficeOperations.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - RareOfficeOperations.yaml index 5f7d2be46b..1e483ccf78 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - RareOfficeOperations.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - RareOfficeOperations.yaml @@ -1,5 +1,5 @@ id: 433c254d-4b84-46f7-99ec-9dfefb5f6a7b -name: Office 365 - Rare and Potentially High-Risk Office Operations +name: GSA Enriched Office 365 - Rare and Potentially High-Risk Office Operations description: | Identifies Office operations that are typically rare and can provide capabilities useful to attackers. severity: Low @@ -19,11 +19,25 @@ relevantTechniques: - T1098 - T1114 query: | - EnrichedMicrosoft365AuditLogs - | where Operation in~ ( "Add-MailboxPermission", "Add-MailboxFolderPermission", "Set-Mailbox", "New-ManagementRoleAssignment", "New-InboxRule", "Set-InboxRule", "Set-TransportRule") - and not(UserId has_any ('NT AUTHORITY\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\SYSTEM (Microsoft.Exchange.AdminApi.NetCore)', 'NT AUTHORITY\\SYSTEM (w3wp)', 'devilfish-applicationaccount') and Operation in~ ( "Add-MailboxPermission", "Set-Mailbox")) - | extend ClientIPOnly = tostring(extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?', dynamic(["IPAddress"]), ClientIp)[0]) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where Operation in~ ( "Add-MailboxPermission", "Add-MailboxFolderPermission", "Set-Mailbox", "New-ManagementRoleAssignment", "New-InboxRule", "Set-InboxRule", "Set-TransportRule") + and not(UserId has_any ('NT AUTHORITY\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\SYSTEM (Microsoft.Exchange.AdminApi.NetCore)', 'NT AUTHORITY\\SYSTEM (w3wp)', 'devilfish-applicationaccount') and Operation in~ ( "Add-MailboxPermission", "Set-Mailbox")) + | extend ClientIPOnly = tostring(extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?', dynamic(["IPAddress"]), ClientIP)[0]) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Operation in~ ( "Add-MailboxPermission", "Add-MailboxFolderPermission", "Set-Mailbox", "New-ManagementRoleAssignment", "New-InboxRule", "Set-InboxRule", "Set-TransportRule") + and not(UserId has_any ('NT AUTHORITY\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\SYSTEM (Microsoft.Exchange.AdminApi.NetCore)', 'NT AUTHORITY\\SYSTEM (w3wp)', 'devilfish-applicationaccount') and Operation in~ ( "Add-MailboxPermission", "Set-Mailbox")) + | extend ClientIPOnly = tostring(extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?', dynamic(["IPAddress"]), ClientIp)[0]) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine and Deduplicate Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(TimeGenerated, *) by Operation, UserId, ClientIPOnly; + // Final Output + CombinedEvents + | project TimeGenerated, Operation, UserId, AccountName, AccountUPNSuffix, ClientIPOnly entityMappings: - entityType: Account fieldMappings: @@ -41,5 +55,5 @@ entityMappings: fieldMappings: - identifier: AppId columnName: AppId -version: 2.0.6 +version: 2.0.7 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewIP.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewIP.yaml index 563af53e1c..c5773d2077 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewIP.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewIP.yaml @@ -1,5 +1,5 @@ id: 7460e34e-4c99-47b2-b7c0-c42e339fc586 -name: Office 365 - SharePoint File Operation via Previously Unseen IPs +name: GSA Enriched Office 365 - SharePoint File Operation via Previously Unseen IPs description: | Identifies anomalies using user behavior by setting a threshold for significant changes in file upload/download activities from new IP addresses. It establishes a baseline of typical behavior, compares it to recent activity, and flags deviations exceeding a default threshold of 25. severity: Medium @@ -17,40 +17,56 @@ tactics: relevantTechniques: - T1030 query: | - let threshold = 0.25; + let threshold = 25; let szSharePointFileOperation = "SharePointFileOperation"; let szOperations = dynamic(["FileDownloaded", "FileUploaded"]); let starttime = 14d; let endtime = 1d; - // Define a baseline of normal user behavior - let userBaseline = EnrichedMicrosoft365AuditLogs - | where TimeGenerated between(ago(starttime)..ago(endtime)) - | where RecordType == szSharePointFileOperation - | where Operation in (szOperations) - | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) - | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) - | where isnotempty(UserAgent) - | summarize Count = count() by UserId, Operation, Site_Url, ClientIp - | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url, ClientIp; - // Get recent user activity - let recentUserActivity = EnrichedMicrosoft365AuditLogs - | where TimeGenerated > ago(endtime) - | where RecordType == szSharePointFileOperation - | where Operation in (szOperations) - | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) - | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) - | where isnotempty(UserAgent) - | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), RecentCount = count() by UserId, UserType, Operation, Site_Url, ClientIp, ObjectId, Workload, UserAgent; - // Join the baseline and recent activity, and calculate the deviation - let UserBehaviorAnalysis = userBaseline - | join kind=inner (recentUserActivity) on UserId, Operation, Site_Url, ClientIp - | extend Deviation = abs(RecentCount - AvgCount) / AvgCount; + // Define a baseline of normal user behavior for OfficeActivity + let userBaselineOffice = OfficeActivity + | where TimeGenerated between(ago(starttime)..ago(endtime)) + | where RecordType =~ szSharePointFileOperation + | where Operation in~ (szOperations) + | where isnotempty(UserAgent) + | summarize Count = count() by UserId, Operation, Site_Url, ClientIP + | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url, ClientIP; + // Get recent user activity for OfficeActivity + let recentUserActivityOffice = OfficeActivity + | where TimeGenerated > ago(endtime) + | where RecordType =~ szSharePointFileOperation + | where Operation in~ (szOperations) + | where isnotempty(UserAgent) + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), RecentCount = count() by UserId, UserType, Operation, Site_Url, ClientIP, OfficeObjectId, OfficeWorkload, UserAgent; + // Define a baseline of normal user behavior for EnrichedMicrosoft365AuditLogs + let userBaselineEnriched = EnrichedMicrosoft365AuditLogs + | where TimeGenerated between(ago(starttime)..ago(endtime)) + | where RecordType == szSharePointFileOperation + | where Operation in (szOperations) + | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) + | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) + | where isnotempty(UserAgent) + | summarize Count = count() by UserId, Operation, Site_Url, ClientIp + | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url, ClientIp; + // Get recent user activity for EnrichedMicrosoft365AuditLogs + let recentUserActivityEnriched = EnrichedMicrosoft365AuditLogs + | where TimeGenerated > ago(endtime) + | where RecordType == szSharePointFileOperation + | where Operation in (szOperations) + | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) + | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) + | where isnotempty(UserAgent) + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), RecentCount = count() by UserId, UserType, Operation, Site_Url, ClientIp, ObjectId, Workload, UserAgent; + // Combine user baselines and recent activity, calculate deviation, and deduplicate + let UserBehaviorAnalysis = userBaselineOffice + | join kind=inner (recentUserActivityOffice) on UserId, Operation, Site_Url, ClientIP + | union (userBaselineEnriched | join kind=inner (recentUserActivityEnriched) on UserId, Operation, Site_Url, ClientIp) + | extend Deviation = abs(RecentCount - AvgCount) / AvgCount; // Filter for significant deviations UserBehaviorAnalysis - | where Deviation > threshold - | project StartTimeUtc, EndTimeUtc, UserId, UserType, Operation, ClientIp, Site_Url, ObjectId, Workload, UserAgent, Deviation, Count=RecentCount - | order by Count desc, ClientIp asc, Operation asc, UserId asc - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | where Deviation > threshold + | project StartTimeUtc, EndTimeUtc, UserId, UserType, Operation, ClientIP, Site_Url, ObjectId, OfficeObjectId, OfficeWorkload, Workload, UserAgent, Deviation, Count=RecentCount + | order by Count desc, ClientIP asc, Operation asc, UserId asc + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); entityMappings: - entityType: Account fieldMappings: @@ -68,5 +84,5 @@ entityMappings: fieldMappings: - identifier: Url columnName: Site_Url -version: 2.0.5 +version: 2.0.6 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml index 2973309921..ea908dd604 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml @@ -1,5 +1,5 @@ id: efd17c5f-5167-40f8-a1e9-0818940785d9 -name: Office 365 - SharePointFileOperation via devices with previously unseen user agents +name: GSA Enriched Office 365 - SharePointFileOperation via devices with previously unseen user agents description: | Identifies anomalies if the number of documents uploaded or downloaded from device(s) associated with a previously unseen user agent exceeds a threshold (default is 5) and deviation (default is 25%). severity: Medium @@ -17,52 +17,78 @@ tactics: relevantTechniques: - T1030 query: | - // Set threshold for the number of downloads/uploads from a new user agent - let threshold = 5; - // Define constants for SharePoint file operations + let threshold = 5; let szSharePointFileOperation = "SharePointFileOperation"; let szOperations = dynamic(["FileDownloaded", "FileUploaded"]); - // Define the historical activity for analysis - let starttime = 14d; // Define the start time for historical data (14 days ago) - let endtime = 1d; // Define the end time for historical data (1 day ago) - // Extract the base events for analysis - let Baseevents = - EnrichedMicrosoft365AuditLogs + let starttime = 14d; + let endtime = 1d; + // OfficeActivity - Base Events + let BaseeventsOffice = OfficeActivity + | where TimeGenerated between (ago(starttime) .. ago(endtime)) + | where RecordType =~ szSharePointFileOperation + | where Operation in~ (szOperations) + | where isnotempty(UserAgent); + // OfficeActivity - Frequent User Agents + let FrequentUAOffice = BaseeventsOffice + | summarize FUACount = count() by UserAgent, RecordType, Operation + | where FUACount >= threshold + | distinct UserAgent; + // OfficeActivity - User Baseline + let UserBaseLineOffice = BaseeventsOffice + | summarize Count = count() by UserId, Operation, Site_Url + | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url; + // OfficeActivity - Recent User Activity + let RecentActivityOffice = OfficeActivity + | where TimeGenerated > ago(endtime) + | where RecordType =~ szSharePointFileOperation + | where Operation in~ (szOperations) + | where isnotempty(UserAgent) + | where UserAgent in~ (FrequentUAOffice) + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), OfficeObjectIdCount = dcount(OfficeObjectId), OfficeObjectIdList = make_set(OfficeObjectId), UserAgentSeenCount = count() + by RecordType, Operation, UserAgent, UserType, UserId, ClientIP, OfficeWorkload, Site_Url; + // EnrichedMicrosoft365AuditLogs - Base Events + let BaseeventsEnriched = EnrichedMicrosoft365AuditLogs | where TimeGenerated between (ago(starttime) .. ago(endtime)) | where RecordType == szSharePointFileOperation | where Operation in (szOperations) | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) | where isnotempty(UserAgent); - // Identify frequently occurring user agents - let FrequentUA = Baseevents + // EnrichedMicrosoft365AuditLogs - Frequent User Agents + let FrequentUAEnriched = BaseeventsEnriched | summarize FUACount = count() by UserAgent, RecordType, Operation | where FUACount >= threshold | distinct UserAgent; - // Calculate a user baseline for further analysis - let UserBaseLine = Baseevents + // EnrichedMicrosoft365AuditLogs - User Baseline + let UserBaseLineEnriched = BaseeventsEnriched | summarize Count = count() by UserId, Operation, Site_Url | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url; - // Extract recent activity for analysis - let RecentActivity = EnrichedMicrosoft365AuditLogs + // EnrichedMicrosoft365AuditLogs - Recent User Activity + let RecentActivityEnriched = EnrichedMicrosoft365AuditLogs | where TimeGenerated > ago(endtime) | where RecordType == szSharePointFileOperation | where Operation in (szOperations) | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) | where isnotempty(UserAgent) - | where UserAgent in (FrequentUA) + | where UserAgent in (FrequentUAEnriched) | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), ObjectIdCount = dcount(ObjectId), ObjectIdList = make_set(ObjectId), UserAgentSeenCount = count() by RecordType, Operation, UserAgent, UserId, ClientIp, Site_Url; - // Analyze user behavior based on baseline and recent activity - let UserBehaviorAnalysis = UserBaseLine - | join kind=inner (RecentActivity) on UserId, Operation, Site_Url + // Combine Baseline and Recent Activity, Calculate Deviation, and Deduplicate + let UserBehaviorAnalysisOffice = UserBaseLineOffice + | join kind=inner (RecentActivityOffice) on UserId, Operation, Site_Url + | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount; + let UserBehaviorAnalysisEnriched = UserBaseLineEnriched + | join kind=inner (RecentActivityEnriched) on UserId, Operation, Site_Url | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount; - // Filter and format results for specific user behavior analysis - UserBehaviorAnalysis + // Combine Office and Enriched Logs + let CombinedUserBehaviorAnalysis = UserBehaviorAnalysisOffice + | union UserBehaviorAnalysisEnriched; + // Filter and Format Final Results + CombinedUserBehaviorAnalysis | where Deviation > 0.25 | extend UserIdName = tostring(split(UserId, '@')[0]), UserIdUPNSuffix = tostring(split(UserId, '@')[1]) - | project-reorder StartTime, EndTime, UserAgent, UserAgentSeenCount, UserId, ClientIp, Site_Url + | project-reorder StartTime, EndTime, UserAgent, UserAgentSeenCount, UserId, ClientIP, Site_Url | order by UserAgentSeenCount desc, UserAgent asc, UserId asc, Site_Url asc entityMappings: - entityType: Account @@ -81,5 +107,5 @@ entityMappings: fieldMappings: - identifier: Url columnName: Site_Url -version: 2.2.5 +version: 2.2.6 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - exchange_auditlogdisabled.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - exchange_auditlogdisabled.yaml index 0db9fcc31d..7d21329a56 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - exchange_auditlogdisabled.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - exchange_auditlogdisabled.yaml @@ -1,5 +1,5 @@ id: dc451755-8ab3-4059-b805-e454c45d1d44 -name: Office 365 - Exchange AuditLog Disabled +name: GSA Enriched Office 365 - Exchange AuditLog Disabled description: | 'Identifies when the exchange audit logging has been disabled which may be an adversary attempt to evade detection or avoid other defenses.' severity: Medium @@ -17,17 +17,38 @@ tactics: relevantTechniques: - T1562 query: | - EnrichedMicrosoft365AuditLogs - | where Workload =~ "Exchange" - | where UserType in~ ("Admin", "DcAdmin") - | where Operation =~ "Set-AdminAuditLogConfig" - | extend AdminAuditLogEnabledValue = tostring(parse_json(tostring(parse_json(tostring(array_slice(parse_json(tostring(AdditionalProperties.Parameters)), 3, 3)))[0])).Value) - | where AdminAuditLogEnabledValue =~ "False" - | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() by Operation, UserType, UserId, ClientIP = SourceIp, ResultStatus, Parameters = tostring(AdditionalProperties.Parameters), AdminAuditLogEnabledValue - | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), UserId) - | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '') - | extend AccountName = iff(UserId contains '\\', tostring(split(UserId, '\\')[1]), AccountName) - | extend AccountNTDomain = iff(UserId contains '\\', tostring(split(UserId, '\\')[0]), '') + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where OfficeWorkload =~ "Exchange" + | where UserType in~ ("Admin", "DcAdmin") + // Only admin or global-admin can disable audit logging + | where Operation =~ "Set-AdminAuditLogConfig" + | extend AdminAuditLogEnabledValue = tostring(parse_json(tostring(parse_json(tostring(array_slice(parse_json(Parameters), 3, 3)))[0])).Value) + | where AdminAuditLogEnabledValue =~ "False" + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() by Operation, UserType, UserId, ClientIP, ResultStatus, Parameters, AdminAuditLogEnabledValue + | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), UserId) + | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '') + | extend AccountName = iff(UserId contains '\\', tostring(split(UserId, '\\')[1]), AccountName) + | extend AccountNTDomain = iff(UserId contains '\\', tostring(split(UserId, '\\')[0]), ''); + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload =~ "Exchange" + | where UserType in~ ("Admin", "DcAdmin") + | where Operation =~ "Set-AdminAuditLogConfig" + | extend AdminAuditLogEnabledValue = tostring(parse_json(tostring(parse_json(tostring(array_slice(parse_json(tostring(AdditionalProperties.Parameters)), 3, 3)))[0])).Value) + | where AdminAuditLogEnabledValue =~ "False" + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() by Operation, UserType, UserId, ClientIP = SourceIp, ResultStatus, Parameters = tostring(AdditionalProperties.Parameters), AdminAuditLogEnabledValue + | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), UserId) + | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '') + | extend AccountName = iff(UserId contains '\\', tostring(split(UserId, '\\')[1]), AccountName) + | extend AccountNTDomain = iff(UserId contains '\\', tostring(split(UserId, '\\')[0]), ''); + // Combine Office and Enriched Events and Deduplicate + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(StartTimeUtc, *) by Operation, UserId, ClientIP; + // Project Final Output + CombinedEvents + | project StartTimeUtc, EndTimeUtc, Operation, UserType, UserId, ClientIP, ResultStatus, Parameters, AdminAuditLogEnabledValue, AccountName, AccountUPNSuffix, AccountNTDomain entityMappings: - entityType: Account fieldMappings: @@ -45,5 +66,5 @@ entityMappings: fieldMappings: - identifier: Address columnName: ClientIP -version: 2.0.7 +version: 2.0.8 kind: Scheduled \ No newline at end of file diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - office_policytampering.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - office_policytampering.yaml index 5dec340460..f07c58f8c8 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - office_policytampering.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - office_policytampering.yaml @@ -1,5 +1,5 @@ id: 0f1f2b17-f9d6-4d2a-a0fb-a7ae1659e3eb -name: Office 365 - Office Policy Tampering +name: GSA Enriched Office 365 - Office Policy Tampering description: | Identifies if any tampering is done to either audit log, ATP Safelink, SafeAttachment, AntiPhish, or Dlp policy. An adversary may use this technique to evade detection or avoid other policy-based defenses. @@ -21,27 +21,54 @@ relevantTechniques: - T1098 - T1562 query: | - let opList = EnrichedMicrosoft365AuditLogs - | summarize by Operation - | where Operation has_any ("Remove", "Disable") - | where Operation contains "AntiPhish" or Operation contains "SafeAttachment" or Operation contains "SafeLinks" or Operation contains "Dlp" or Operation contains "Audit" - | summarize make_set(Operation, 500); - EnrichedMicrosoft365AuditLogs - | where RecordType == "ExchangeAdmin" - | where UserType in~ ("Admin", "DcAdmin") - | where Operation in~ (opList) - | extend ClientIPOnly = case( - ClientIp has ".", tostring(split(ClientIp, ":")[0]), - ClientIp has "[", tostring(trim_start(@'[[]', tostring(split(ClientIp, "]")[0]))), - ClientIp - ) - | extend Port = case( - ClientIp has ".", tostring(split(ClientIp, ":")[1]), - ClientIp has "[", tostring(split(ClientIp, "]:")[1]), - "" - ) - | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() by Operation, UserType, UserId, ClientIP = ClientIPOnly, Port, ResultStatus, Parameters = tostring(AdditionalProperties.Parameters) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + // Query for EnrichedMicrosoft365AuditLogs + let enrichedOpList = EnrichedMicrosoft365AuditLogs + | summarize by Operation + | where Operation has_any ("Remove", "Disable") + | where Operation contains "AntiPhish" or Operation contains "SafeAttachment" or Operation contains "SafeLinks" or Operation contains "Dlp" or Operation contains "Audit" + | summarize make_set(Operation, 500); + let enrichedLogs = EnrichedMicrosoft365AuditLogs + | where RecordType == "ExchangeAdmin" + | where UserType in~ ("Admin", "DcAdmin") + | where Operation in~ (enrichedOpList) + | extend ClientIPOnly = case( + ClientIp has ".", tostring(split(ClientIp, ":")[0]), + ClientIp has "[", tostring(trim_start(@'[[]', tostring(split(ClientIp, "]")[0]))), + ClientIp + ) + | extend Port = case( + ClientIp has ".", tostring(split(ClientIp, ":")[1]), + ClientIp has "[", tostring(split(ClientIp, "]:")[1]), + "" + ) + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() by Operation, UserType, UserId, ClientIP = ClientIPOnly, Port, ResultStatus, Parameters = tostring(AdditionalProperties.Parameters) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Query for OfficeActivity + let officeOpList = OfficeActivity + | summarize by Operation + | where Operation has_any ("Remove", "Disable") + | where Operation contains "AntiPhish" or Operation contains "SafeAttachment" or Operation contains "SafeLinks" or Operation contains "Dlp" or Operation contains "Audit" + | summarize make_set(Operation, 500); + let officeLogs = OfficeActivity + | where RecordType =~ "ExchangeAdmin" + | where UserType in~ ("Admin","DcAdmin") + | where Operation in~ (officeOpList) + | extend ClientIPOnly = case( + ClientIP has ".", tostring(split(ClientIP,":")[0]), + ClientIP has "[", tostring(trim_start(@'[[]',tostring(split(ClientIP,"]")[0]))), + ClientIP + ) + | extend Port = case( + ClientIP has ".", tostring(split(ClientIP,":")[1]), + ClientIP has "[", tostring(split(ClientIP,"]:")[1]), + "" + ) + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() by Operation, UserType, UserId, ClientIP = ClientIPOnly, Port, ResultStatus, Parameters + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine Enriched Logs and Office Activity Logs + union isfuzzy=true enrichedLogs, officeLogs + | summarize StartTimeUtc = min(StartTimeUtc), EndTimeUtc = max(EndTimeUtc), TotalOperationCount = sum(OperationCount) by Operation, UserType, UserId, ClientIP, Port, ResultStatus, Parameters, AccountName, AccountUPNSuffix + | order by StartTimeUtc desc; entityMappings: - entityType: Account fieldMappings: @@ -55,5 +82,5 @@ entityMappings: fieldMappings: - identifier: Address columnName: ClientIP -version: 2.0.4 +version: 2.0.6 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml index 418804b604..02dfc1c3b6 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml @@ -1,5 +1,5 @@ id: 30375d00-68cc-4f95-b89a-68064d566358 -name: Office 365 - Sharepoint File Transfer Above Threshold +name: GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold description: | Identifies Office365 Sharepoint File Transfers above a certain threshold in a 15-minute time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur. @@ -18,12 +18,33 @@ relevantTechniques: - T1020 query: | let threshold = 5000; - EnrichedMicrosoft365AuditLogs - | where Workload has_any("SharePoint", "OneDrive") and Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | summarize count_distinct_ObjectId=dcount(ObjectId), fileslist=make_set(ObjectId, 10000) by UserId, ClientIp, bin(TimeGenerated, 15m) - | where count_distinct_ObjectId >= threshold - | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField","_", tostring(hash(tostring(fileslist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload has_any("SharePoint", "OneDrive") + | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | summarize count_distinct_ObjectId=dcount(ObjectId), fileslist=make_set(ObjectId, 10000) + by UserId, ClientIp, bin(TimeGenerated, 15m) + | where count_distinct_ObjectId >= threshold + | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where EventSource == "SharePoint" + | where OfficeWorkload has_any("SharePoint", "OneDrive") + | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | summarize count_distinct_OfficeObjectId=dcount(OfficeObjectId), fileslist=make_set(OfficeObjectId, 10000) + by UserId, ClientIP, bin(TimeGenerated, 15m) + | where count_distinct_OfficeObjectId >= threshold + | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(TimeGenerated, *) by UserId, ClientIP; + // Final Output + CombinedEvents + | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId + | order by TimeGenerated desc entityMappings: - entityType: Account fieldMappings: @@ -55,5 +76,5 @@ incidentConfiguration: - Account groupByAlertDetails: [] groupByCustomDetails: [] -version: 1.0.5 +version: 1.0.6 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml index 3ed1db4eff..e5bbb44ee0 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml @@ -1,5 +1,5 @@ id: abd6976d-8f71-4851-98c4-4d086201319c -name: Office 365 - Sharepoint File Transfer Above Threshold +name: GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold description: | Identifies Office365 Sharepoint File Transfers with a distinct folder count above a certain threshold in a 15-minute time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur. @@ -17,15 +17,36 @@ tactics: relevantTechniques: - T1020 query: | - let threshold = 500; - EnrichedMicrosoft365AuditLogs - | where Workload has_any("SharePoint", "OneDrive") and Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | extend EventSource = tostring(parse_json(tostring(AdditionalProperties)).EventSource) - | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) - | summarize count_distinct_ObjectId = dcount(ObjectId), dirlist = make_set(ObjectId, 10000) by UserId, ClientIp, UserAgent, bin(TimeGenerated, 15m) - | where count_distinct_ObjectId >= threshold - | extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField","_", tostring(hash(tostring(dirlist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + let threshold = 500; + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where EventSource == "SharePoint" + | where OfficeWorkload has_any("SharePoint", "OneDrive") + | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | summarize count_distinct_SourceRelativeUrl = dcount(SourceRelativeUrl), dirlist = make_set(SourceRelativeUrl, 10000) + by UserId, ClientIP, UserAgent, bin(TimeGenerated, 15m) + | where count_distinct_SourceRelativeUrl >= threshold + | extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField", "_", tostring(hash(tostring(dirlist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload has_any("SharePoint", "OneDrive") + | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | extend EventSource = tostring(parse_json(tostring(AdditionalProperties)).EventSource) + | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) + | summarize count_distinct_ObjectId = dcount(ObjectId), dirlist = make_set(ObjectId, 10000) + by UserId, ClientIp, UserAgent, bin(TimeGenerated, 15m) + | where count_distinct_ObjectId >= threshold + | extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField", "_", tostring(hash(tostring(dirlist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(TimeGenerated, *) by UserId, ClientIP, UserAgent; + // Final Output + CombinedEvents + | project TimeGenerated, UserId, ClientIP, UserAgent, AccountName, AccountUPNSuffix, DirSample, count_distinct_SourceRelativeUrl + | order by TimeGenerated desc entityMappings: - entityType: Account fieldMappings: @@ -57,5 +78,5 @@ incidentConfiguration: - Account groupByAlertDetails: [] groupByCustomDetails: [] -version: 1.0.5 +version: 1.0.6 kind: Scheduled From d2e692ce07763fe5605615ea66a89e6e2653b5e4 Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Wed, 2 Oct 2024 09:53:06 +0300 Subject: [PATCH 02/27] fixed YAMLs --- ... to Team and immediately uploads file.yaml | 88 +++++------ ...365 - ExternalUserAddedRemovedInTeams.yaml | 8 +- ... Mail_redirect_via_ExO_transport_rule.yaml | 84 +++++------ .../Office 365 - Malicious_Inbox_Rule.yaml | 75 +++++---- .../Office 365 - MultipleTeamsDeletes.yaml | 52 +++---- .../Office 365 - Office_MailForwarding.yaml | 116 +++++++------- ...ice 365 - Office_Uploaded_Executables.yaml | 142 +++++++++--------- .../Office 365 - RareOfficeOperations.yaml | 21 +-- ...ce 365 - SharePoint_Downloads_byNewIP.yaml | 10 +- ...- SharePoint_Downloads_byNewUserAgent.yaml | 19 +-- ...ffice 365 - exchange_auditlogdisabled.yaml | 74 ++++----- .../Office 365 - office_policytampering.yaml | 81 ++++++---- ...repoint_file_transfer_above_threshold.yaml | 20 ++- ...file_transfer_folders_above_threshold.yaml | 67 +++++---- 14 files changed, 460 insertions(+), 397 deletions(-) diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - External User added to Team and immediately uploads file.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - External User added to Team and immediately uploads file.yaml index 2dc1409234..e9fa0d76fe 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - External User added to Team and immediately uploads file.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - External User added to Team and immediately uploads file.yaml @@ -17,7 +17,7 @@ tactics: relevantTechniques: - T1566 query: | - let fileAccessThreshold = 10; + let fileAccessThreshold = 10; // OfficeActivity Query let OfficeEvents = OfficeActivity | where OfficeWorkload =~ "MicrosoftTeams" @@ -50,49 +50,49 @@ query: | ) on OfficeObjectId ) on MemberAdded | project TimeAdded, TimeDeleted, MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName; - // EnrichedMicrosoft365AuditLogs Query - let EnrichedEvents = EnrichedMicrosoft365AuditLogs - | where Workload =~ "MicrosoftTeams" - | where Operation =~ "MemberAdded" - | extend MemberAdded = tostring(parse_json(tostring(AdditionalProperties)).Members[0].UPN) - | where MemberAdded contains "#EXT#" - | project TimeAdded = TimeGenerated, Operation, MemberAdded, UserWhoAdded = UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName) - | join kind=inner ( - EnrichedMicrosoft365AuditLogs - | where Workload =~ "MicrosoftTeams" - | where Operation =~ "MemberRemoved" - | extend MemberAdded = tostring(parse_json(tostring(AdditionalProperties)).Members[0].UPN) - | where MemberAdded contains "#EXT#" - | project TimeDeleted = TimeGenerated, Operation, MemberAdded, UserWhoDeleted = UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName) - ) on MemberAdded, TeamName - | where TimeDeleted > TimeAdded - | join kind=inner ( - EnrichedMicrosoft365AuditLogs - | where RecordType == "SharePointFileOperation" - | where Operation == "FileUploaded" - | extend MemberAdded = UserId, SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl), TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName) - | where SourceRelativeUrl has "Microsoft Teams Chat Files" - | join kind=inner ( - EnrichedMicrosoft365AuditLogs - | where RecordType == "SharePointFileOperation" - | where Operation == "FileAccessed" - | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl), TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName) - | where SourceRelativeUrl has "Microsoft Teams Chat Files" - | summarize FileAccessCount = count() by ObjectId, TeamName - | where FileAccessCount > fileAccessThreshold - ) on ObjectId, TeamName - ) on MemberAdded, TeamName - | project TimeAdded, TimeDeleted, MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName; - // Combine Office and Enriched Events and Deduplicate - let CombinedEvents = OfficeEvents - | union EnrichedEvents - | summarize arg_min(TimeAdded, *) by MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName; - // Project Final Output - CombinedEvents - | extend MemberAddedAccountName = tostring(split(MemberAdded, "@")[0]), MemberAddedAccountUPNSuffix = tostring(split(MemberAdded, "@")[1]) - | extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, "@")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, "@")[1]) - | extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, "@")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, "@")[1]) - | project TimeAdded, TimeDeleted, MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName, MemberAddedAccountName, MemberAddedAccountUPNSuffix, UserWhoAddedAccountName, UserWhoAddedAccountUPNSuffix, UserWhoDeletedAccountName, UserWhoDeletedAccountUPNSuffix + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload =~ "MicrosoftTeams" + | where Operation =~ "MemberAdded" + | extend MemberAdded = tostring(parse_json(tostring(AdditionalProperties)).Members[0].UPN) + | where MemberAdded contains "#EXT#" + | project TimeAdded = TimeGenerated, Operation, MemberAdded, UserWhoAdded = UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName) + | join kind=inner ( + EnrichedMicrosoft365AuditLogs + | where Workload =~ "MicrosoftTeams" + | where Operation =~ "MemberRemoved" + | extend MemberAdded = tostring(parse_json(tostring(AdditionalProperties)).Members[0].UPN) + | where MemberAdded contains "#EXT#" + | project TimeDeleted = TimeGenerated, Operation, MemberAdded, UserWhoDeleted = UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName) + ) on MemberAdded, TeamName + | where TimeDeleted > TimeAdded + | join kind=inner ( + EnrichedMicrosoft365AuditLogs + | where RecordType == "SharePointFileOperation" + | where Operation == "FileUploaded" + | extend MemberAdded = UserId, SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl), TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName) + | where SourceRelativeUrl has "Microsoft Teams Chat Files" + | join kind=inner ( + EnrichedMicrosoft365AuditLogs + | where RecordType == "SharePointFileOperation" + | where Operation == "FileAccessed" + | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl), TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName) + | where SourceRelativeUrl has "Microsoft Teams Chat Files" + | summarize FileAccessCount = count() by ObjectId, TeamName + | where FileAccessCount > fileAccessThreshold + ) on ObjectId, TeamName + ) on MemberAdded, TeamName + | project TimeAdded, TimeDeleted, MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName; + // Combine Office and Enriched Events and Deduplicate + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(TimeAdded, *) by MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName; + // Project Final Output + CombinedEvents + | extend MemberAddedAccountName = tostring(split(MemberAdded, "@")[0]), MemberAddedAccountUPNSuffix = tostring(split(MemberAdded, "@")[1]) + | extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, "@")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, "@")[1]) + | extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, "@")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, "@")[1]) + | project TimeAdded, TimeDeleted, MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName, MemberAddedAccountName, MemberAddedAccountUPNSuffix, UserWhoAddedAccountName, UserWhoAddedAccountUPNSuffix, UserWhoDeletedAccountName, UserWhoDeletedAccountUPNSuffix entityMappings: - entityType: Account fieldMappings: diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - ExternalUserAddedRemovedInTeams.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - ExternalUserAddedRemovedInTeams.yaml index b7d16137d2..c2220e61a0 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - ExternalUserAddedRemovedInTeams.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - ExternalUserAddedRemovedInTeams.yaml @@ -35,7 +35,7 @@ query: | | mv-expand Members = parse_json(tostring(AdditionalProperties.Members)) | extend UPN = tostring(Members.UPN) | where UPN has ("#EXT#") - | project TimeGenerated, Operation, UPN, UserId, TeamName = tostring(AdditionalProperties.TeamName), ClientIP = SourceIp + | project TimeGenerated, Operation, UPN, UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName), ClientIP = SourceIp }; let TeamsAddOffice = TeamsAddDelOffice("MemberAdded") | project TimeAdded = TimeGenerated, Operation, MemberAdded = UPN, UserWhoAdded = UserId, TeamName, ClientIP; @@ -66,7 +66,7 @@ entityMappings: - identifier: Name columnName: MemberAdded_RemovedAccountName - identifier: UPNSuffix - columnName: MemberAddedAccountUPNSuffix + columnName: MemberAdded_RemovedAccountUPNSuffix - entityType: Account fieldMappings: - identifier: FullName @@ -86,6 +86,6 @@ entityMappings: - entityType: IP fieldMappings: - identifier: Address - columnName: ClientIp + columnName: ClientIP version: 2.1.4 -kind: Scheduled \ No newline at end of file +kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml index 9c86a9f4da..8927e62141 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml @@ -1,8 +1,8 @@ id: edcfc2e0-3134-434c-8074-9101c530d419 name: GSA Enriched Office 365 - Mail redirect via ExO transport rule description: | - 'Identifies when Exchange Online transport rule configured to forward emails. - This could be an adversary mailbox configured to collect mail from multiple user accounts.' + Identifies when Exchange Online transport rule configured to forward emails. + This could be an adversary mailbox configured to collect mail from multiple user accounts. severity: Medium status: Available requiredDataConnectors: @@ -20,46 +20,44 @@ relevantTechniques: - T1114 - T1020 query: | - let officeActivityQuery = OfficeActivity - | where OfficeWorkload == "Exchange" - | where Operation in~ ("New-TransportRule", "Set-TransportRule") - | mv-apply DynamicParameters = todynamic(Parameters) on (summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))) - | extend RuleName = case( - Operation =~ "Set-TransportRule", OfficeObjectId, - Operation =~ "New-TransportRule", ParsedParameters.Name, - "Unknown") - | mv-expand ExpandedParameters = todynamic(Parameters) - | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) - | extend RedirectTo = ExpandedParameters.Value - | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] - | extend From = ParsedParameters.From - | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - - let enrichedLogsQuery = EnrichedMicrosoft365AuditLogs - | where Workload == "Exchange" - | where Operation in~ ("New-TransportRule", "Set-TransportRule") - | mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on ( - summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) - ) - | extend RuleName = case( - Operation =~ "Set-TransportRule", ObjectId, // Assuming ObjectId maps to OfficeObjectId - Operation =~ "New-TransportRule", ParsedParameters.Name, - "Unknown" - ) - | mv-expand ExpandedParameters = todynamic(tostring(AdditionalProperties.Parameters)) - | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) - | extend RedirectTo = ExpandedParameters.Value - | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIp)[0] - | extend From = ParsedParameters.From - | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters = tostring(AdditionalProperties.Parameters) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - - // Combine both queries - union isfuzzy=true officeActivityQuery, enrichedLogsQuery - | summarize arg_min(TimeGenerated, *) by RuleName, RedirectTo - | project TimeGenerated, RedirectTo, IPAddress, Port, UserId, From, Operation, RuleName, Parameters, AccountName, AccountUPNSuffix - | order by TimeGenerated desc; + let officeActivityQuery = OfficeActivity + | where OfficeWorkload == "Exchange" + | where Operation in~ ("New-TransportRule", "Set-TransportRule") + | mv-apply DynamicParameters = todynamic(Parameters) on (summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))) + | extend RuleName = case( + Operation =~ "Set-TransportRule", OfficeObjectId, + Operation =~ "New-TransportRule", ParsedParameters.Name, + "Unknown") + | mv-expand ExpandedParameters = todynamic(Parameters) + | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) + | extend RedirectTo = ExpandedParameters.Value + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] + | extend From = ParsedParameters.From + | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + let enrichedLogsQuery = EnrichedMicrosoft365AuditLogs + | where Workload == "Exchange" + | where Operation in~ ("New-TransportRule", "Set-TransportRule") + | mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on ( + summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) + ) + | extend RuleName = case( + Operation =~ "Set-TransportRule", ObjectId, // Assuming ObjectId maps to OfficeObjectId + Operation =~ "New-TransportRule", ParsedParameters.Name, + "Unknown" + ) + | mv-expand ExpandedParameters = todynamic(tostring(AdditionalProperties.Parameters)) + | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) + | extend RedirectTo = ExpandedParameters.Value + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] + | extend From = ParsedParameters.From + | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters = tostring(AdditionalProperties.Parameters) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine both queries + union isfuzzy=true officeActivityQuery, enrichedLogsQuery + | summarize arg_min(TimeGenerated, *) by RuleName, RedirectTo + | project TimeGenerated, RedirectTo, IPAddress, Port, UserId, From, Operation, RuleName, Parameters, AccountName, AccountUPNSuffix + | order by TimeGenerated desc; entityMappings: - entityType: Account fieldMappings: @@ -73,5 +71,5 @@ entityMappings: fieldMappings: - identifier: Address columnName: IPAddress -version: 2.0.7 +version: 2.1.4 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Malicious_Inbox_Rule.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Malicious_Inbox_Rule.yaml index efdb71967e..2dd383aec3 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Malicious_Inbox_Rule.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Malicious_Inbox_Rule.yaml @@ -1,9 +1,9 @@ id: a9c76c8d-f60d-49ec-9b1f-bdfee6db3807 name: GSA Enriched Office 365 - Malicious Inbox Rule description: | - 'Often times after the initial compromise the attackers create inbox rules to delete emails that contain certain keywords. - This is done so as to limit ability to warn compromised users that they've been compromised. Below is a sample query that tries to detect this. - Reference: https://www.reddit.com/r/sysadmin/comments/7kyp0a/recent_phishing_attempts_my_experience_and_what/' + Often times after the initial compromise the attackers create inbox rules to delete emails that contain certain keywords. + This is done so as to limit ability to warn compromised users that they've been compromised. Below is a sample query that tries to detect this. + Reference: https://www.reddit.com/r/sysadmin/comments/7kyp0a/recent_phishing_attempts_my_experience_and_what/ severity: Medium status: Available requiredDataConnectors: @@ -21,44 +21,69 @@ relevantTechniques: - T1098 - T1078 query: | - let Keywords = dynamic(["helpdesk", "alert", "suspicious", "fake", "malicious", "phishing", "spam", "do not click", "do not open", "hijacked", "Fatal"]); - // OfficeActivity Query - let OfficeEvents = OfficeActivity + let Keywords = dynamic(["helpdesk", "alert", "suspicious", "fake", "malicious", "phishing", "spam", "do not click", "do not open", "hijacked", "Fatal"]); + // OfficeActivity Query + let OfficeEvents = OfficeActivity | where OfficeWorkload =~ "Exchange" | where Operation =~ "New-InboxRule" and (ResultStatus =~ "True" or ResultStatus =~ "Succeeded") - | where Parameters has "Deleted Items" or Parameters has "Junk Email" or Parameters has "DeleteMessage" + | where Parameters has "Deleted Items" or Parameters has "Junk Email" or Parameters has "DeleteMessage" | extend Events = todynamic(Parameters) - | parse Events with * "SubjectContainsWords" SubjectContainsWords '}'* - | parse Events with * "BodyContainsWords" BodyContainsWords '}'* - | parse Events with * "SubjectOrBodyContainsWords" SubjectOrBodyContainsWords '}'* + | parse Events with * "SubjectContainsWords" SubjectContainsWords '}'* + | parse Events with * "BodyContainsWords" BodyContainsWords '}'* + | parse Events with * "SubjectOrBodyContainsWords" SubjectOrBodyContainsWords '}'* | where SubjectContainsWords has_any (Keywords) or BodyContainsWords has_any (Keywords) or SubjectOrBodyContainsWords has_any (Keywords) - | extend ClientIPAddress = case(ClientIP has ".", tostring(split(ClientIP, ":")[0]), ClientIP has "[", tostring(trim_start(@'[[]', tostring(split(ClientIP, "]")[0]))), ClientIP) + | extend ClientIPAddress = case( + ClientIP has ".", tostring(split(ClientIP, ":")[0]), + ClientIP has "[", tostring(trim_start(@'[[]', tostring(split(ClientIP, "]")[0]))), + ClientIP + ) | extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords)) - | extend RuleDetail = case(OfficeObjectId contains '/', tostring(split(OfficeObjectId, '/')[-1]), tostring(split(OfficeObjectId, '\\')[-1])) + | extend RuleDetail = case( + OfficeObjectId contains '/', tostring(split(OfficeObjectId, '/')[-1]), + OfficeObjectId contains '\\', tostring(split(OfficeObjectId, '\\')[-1]), + "Unknown" + ) | summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Operation, UserId, ClientIPAddress, ResultStatus, Keyword, OriginatingServer, OfficeObjectId, RuleDetail | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) | extend OriginatingServerName = tostring(split(OriginatingServer, " ")[0]); - // EnrichedMicrosoft365AuditLogs Query - let EnrichedEvents = EnrichedMicrosoft365AuditLogs + + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs | where Workload =~ "Exchange" | where Operation =~ "New-InboxRule" and (ResultStatus =~ "True" or ResultStatus =~ "Succeeded") - | where tostring(parse_json(tostring(AdditionalProperties)).Parameters) has "Deleted Items" or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has "Junk Email" or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has "DeleteMessage" + | where tostring(parse_json(tostring(AdditionalProperties)).Parameters) has "Deleted Items" + or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has "Junk Email" + or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has "DeleteMessage" | extend Events = parse_json(tostring(AdditionalProperties)).Parameters - | extend SubjectContainsWords = tostring(Events.SubjectContainsWords), BodyContainsWords = tostring(Events.BodyContainsWords), SubjectOrBodyContainsWords = tostring(Events.SubjectOrBodyContainsWords) - | where SubjectContainsWords has_any (Keywords) or BodyContainsWords has_any (Keywords) or SubjectOrBodyContainsWords has_any (Keywords) - | extend ClientIPAddress = case(ClientIp has ".", tostring(split(ClientIp, ":")[0]), ClientIp has "[", tostring(trim_start(@'[[]', tostring(split(ClientIp, "]")[0]))), ClientIp) + | extend SubjectContainsWords = tostring(Events.SubjectContainsWords), + BodyContainsWords = tostring(Events.BodyContainsWords), + SubjectOrBodyContainsWords = tostring(Events.SubjectOrBodyContainsWords) + | where SubjectContainsWords has_any (Keywords) + or BodyContainsWords has_any (Keywords) + or SubjectOrBodyContainsWords has_any (Keywords) + | extend ClientIPAddress = case( + ClientIp has ".", tostring(split(ClientIp, ":")[0]), + ClientIp has "[", tostring(trim_start(@'[[]', tostring(split(ClientIp, "]")[0]))), + ClientIp + ) | extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords)) - | extend RuleDetail = case(ObjectId contains '/', tostring(split(ObjectId, '/')[-1]), tostring(split(ObjectId, '\\')[-1])) + | extend RuleDetail = case( + ObjectId contains '/', tostring(split(ObjectId, '/')[-1]), + ObjectId contains '\\', tostring(split(ObjectId, '\\')[-1]), + "Unknown" + ) | summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Operation, UserId, ClientIPAddress, ResultStatus, Keyword, ObjectId, RuleDetail | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // Combine and Deduplicate - let CombinedEvents = OfficeEvents + + // Combine and Deduplicate + let CombinedEvents = OfficeEvents | union EnrichedEvents | summarize arg_min(StartTimeUtc, *) by Operation, UserId, ClientIPAddress; - // Final Output - CombinedEvents + + // Final Output + CombinedEvents | project StartTimeUtc, EndTimeUtc, Operation, UserId, ClientIPAddress, ResultStatus, Keyword, RuleDetail, AccountName, AccountUPNSuffix; entityMappings: - entityType: Account @@ -69,10 +94,6 @@ entityMappings: columnName: AccountName - identifier: UPNSuffix columnName: AccountUPNSuffix - - entityType: Host - fieldMappings: - - identifier: FullName - columnName: OriginatingServer - entityType: IP fieldMappings: - identifier: Address diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - MultipleTeamsDeletes.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - MultipleTeamsDeletes.yaml index c5992b559f..789f90df15 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - MultipleTeamsDeletes.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - MultipleTeamsDeletes.yaml @@ -1,8 +1,8 @@ id: db60e4b6-a845-4f28-a18c-94ebbaad6c6c name: GSA Enriched Office 365 - Multiple Teams deleted by a single user description: | - 'This detection flags the occurrences of deleting multiple teams within an hour. - This data is a part of Office 365 Connector in Microsoft Sentinel.' + This detection flags the occurrences of deleting multiple teams within a day. + This data is a part of Office 365 Connector in Microsoft Sentinel. severity: Low status: Available requiredDataConnectors: @@ -19,29 +19,29 @@ relevantTechniques: - T1485 - T1489 query: | - // Set the maximum number of deleted teams to flag suspicious activity - let max_delete_count = 3; - // EnrichedMicrosoft365AuditLogs Query - let EnrichedEvents = EnrichedMicrosoft365AuditLogs - | where Workload =~ "MicrosoftTeams" - | where Operation =~ "TeamDeleted" - | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DeletedTeams = make_set(tostring(AdditionalProperties.TeamName), 1000) by UserId - | where array_length(DeletedTeams) > max_delete_count - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // OfficeActivity Query - let OfficeEvents = OfficeActivity - | where OfficeWorkload =~ "MicrosoftTeams" - | where Operation =~ "TeamDeleted" - | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DeletedTeams = make_set(TeamName, 1000) by UserId - | where array_length(DeletedTeams) > max_delete_count - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // Combine and Deduplicate Office and Enriched Logs - let CombinedEvents = OfficeEvents - | union EnrichedEvents - | summarize arg_min(StartTime, *) by UserId; - // Final Output - CombinedEvents - | project StartTime, EndTime, DeletedTeams, UserId, AccountName, AccountUPNSuffix + // Set the maximum number of deleted teams to flag suspicious activity + let max_delete_count = 3; + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload =~ "MicrosoftTeams" + | where Operation =~ "TeamDeleted" + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DeletedTeams = make_set(tostring(AdditionalProperties.TeamName), 1000) by UserId + | where array_length(DeletedTeams) > max_delete_count + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where OfficeWorkload =~ "MicrosoftTeams" + | where Operation =~ "TeamDeleted" + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DeletedTeams = make_set(TeamName, 1000) by UserId + | where array_length(DeletedTeams) > max_delete_count + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine and Deduplicate Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(StartTime, *) by UserId; + // Final Output + CombinedEvents + | project StartTime, EndTime, DeletedTeams, UserId, AccountName, AccountUPNSuffix entityMappings: - entityType: Account fieldMappings: @@ -52,4 +52,4 @@ entityMappings: - identifier: UPNSuffix columnName: AccountUPNSuffix version: 2.0.6 -kind: Scheduled \ No newline at end of file +kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_MailForwarding.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_MailForwarding.yaml index d56fff5a6f..1ab3097e01 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_MailForwarding.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_MailForwarding.yaml @@ -20,64 +20,64 @@ relevantTechniques: - T1114 - T1020 query: | - // Set query parameters - let queryfrequency = 1d; - let queryperiod = 7d; - // EnrichedMicrosoft365AuditLogs Query - let EnrichedEvents = EnrichedMicrosoft365AuditLogs - | where TimeGenerated > ago(queryperiod) - | where Workload =~ "Exchange" - // Uncomment or adjust the following line based on actual field usage - // | where Operation in ("Set-Mailbox", "New-InboxRule", "Set-InboxRule") - | where tostring(AdditionalProperties.Parameters) has_any ("ForwardTo", "RedirectTo", "ForwardingSmtpAddress") - | mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on ( - summarize ParsedParameters = make_bag(bag_pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) - ) - | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source') - | extend DestinationMailAddress = tolower(case( - isnotempty(column_ifexists("ForwardTo", "")), column_ifexists("ForwardTo", ""), - isnotempty(column_ifexists("RedirectTo", "")), column_ifexists("RedirectTo", ""), - isnotempty(column_ifexists("ForwardingSmtpAddress", "")), trim_start(@"smtp:", column_ifexists("ForwardingSmtpAddress", "")), - "")) - | where isnotempty(DestinationMailAddress) - | mv-expand split(DestinationMailAddress, ";") - | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIp)[0] - | extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]) - | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP - | where DistinctUserCount > 1 and EndTime > ago(queryfrequency) - | mv-expand UserId to typeof(string) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // OfficeActivity Query - let OfficeEvents = OfficeActivity - | where TimeGenerated > ago(queryperiod) - | where OfficeWorkload =~ "Exchange" - // Uncomment or adjust the following line based on actual field usage - // | where Operation in ("Set-Mailbox", "New-InboxRule", "Set-InboxRule") - | where Parameters has_any ("ForwardTo", "RedirectTo", "ForwardingSmtpAddress") - | mv-apply DynamicParameters = todynamic(Parameters) on ( - summarize ParsedParameters = make_bag(bag_pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) - ) - | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source') - | extend DestinationMailAddress = tolower(case( - isnotempty(column_ifexists("ForwardTo", "")), column_ifexists("ForwardTo", ""), - isnotempty(column_ifexists("RedirectTo", "")), column_ifexists("RedirectTo", ""), - isnotempty(column_ifexists("ForwardingSmtpAddress", "")), trim_start(@"smtp:", column_ifexists("ForwardingSmtpAddress", "")), - "")) - | where isnotempty(DestinationMailAddress) - | mv-expand split(DestinationMailAddress, ";") - | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] - | extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]) - | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP - | where DistinctUserCount > 1 and EndTime > ago(queryfrequency) - | mv-expand UserId to typeof(string) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // Combine and Deduplicate Office and Enriched Logs - let CombinedEvents = OfficeEvents - | union EnrichedEvents - | summarize arg_min(StartTime, *) by tostring(DestinationMailAddress), ClientIP; - // Final Output - CombinedEvents - | project StartTime, EndTime, DestinationMailAddress, ClientIP, DistinctUserCount, UserId, Ports, EventCount, AccountName, AccountUPNSuffix + // Set query parameters + let queryfrequency = 1d; + let queryperiod = 7d; + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where TimeGenerated > ago(queryperiod) + | where Workload =~ "Exchange" + // Uncomment or adjust the following line based on actual field usage + // | where Operation in ("Set-Mailbox", "New-InboxRule", "Set-InboxRule") + | where tostring(AdditionalProperties.Parameters) has_any ("ForwardTo", "RedirectTo", "ForwardingSmtpAddress") + | mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on ( + summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) + ) + | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source') + | extend DestinationMailAddress = tolower(case( + isnotempty(column_ifexists("ForwardTo", "")), column_ifexists("ForwardTo", ""), + isnotempty(column_ifexists("RedirectTo", "")), column_ifexists("RedirectTo", ""), + isnotempty(column_ifexists("ForwardingSmtpAddress", "")), trim_start(@"smtp:", column_ifexists("ForwardingSmtpAddress", "")), + "")) + | where isnotempty(DestinationMailAddress) + | mv-expand split(DestinationMailAddress, ";") + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIp)[0] + | extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]) + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP + | where DistinctUserCount > 1 and EndTime > ago(queryfrequency) + | mv-expand UserId to typeof(string) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where TimeGenerated > ago(queryperiod) + | where OfficeWorkload =~ "Exchange" + // Uncomment or adjust the following line based on actual field usage + // | where Operation in ("Set-Mailbox", "New-InboxRule", "Set-InboxRule") + | where Parameters has_any ("ForwardTo", "RedirectTo", "ForwardingSmtpAddress") + | mv-apply DynamicParameters = todynamic(Parameters) on ( + summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) + ) + | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source') + | extend DestinationMailAddress = tolower(case( + isnotempty(column_ifexists("ForwardTo", "")), column_ifexists("ForwardTo", ""), + isnotempty(column_ifexists("RedirectTo", "")), column_ifexists("RedirectTo", ""), + isnotempty(column_ifexists("ForwardingSmtpAddress", "")), trim_start(@"smtp:", column_ifexists("ForwardingSmtpAddress", "")), + "")) + | where isnotempty(DestinationMailAddress) + | mv-expand split(DestinationMailAddress, ";") + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] + | extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]) + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP + | where DistinctUserCount > 1 and EndTime > ago(queryfrequency) + | mv-expand UserId to typeof(string) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine and Deduplicate Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(StartTime, *) by tostring(DestinationMailAddress), ClientIP; + // Final Output + CombinedEvents + | project StartTime, EndTime, DestinationMailAddress, ClientIP, DistinctUserCount, UserId, Ports, EventCount, AccountName, AccountUPNSuffix entityMappings: - entityType: Account fieldMappings: diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_Uploaded_Executables.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_Uploaded_Executables.yaml index ecb8cc0365..954d1961c1 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_Uploaded_Executables.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_Uploaded_Executables.yaml @@ -22,73 +22,79 @@ relevantTechniques: - T1105 - T1570 query: | - let threshold = 2; - let uploadOp = 'FileUploaded'; - // Extensions that are interesting. Add/Remove to this list as you see fit - let execExt = dynamic(['exe', 'inf', 'gzip', 'cmd', 'bat']); - let starttime = 8d; - let endtime = 1d; - // OfficeActivity Query - let OfficeEvents = OfficeActivity - | where TimeGenerated >= ago(endtime) - | where Operation =~ uploadOp - | where SourceFileExtension has_any (execExt) - | extend RecordType = coalesce(tostring(RecordType), "UnknownRecordType") // Ensure RecordType is a string - | project TimeGenerated, OfficeId, OfficeWorkload = OfficeWorkload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, UserAgent, Site_Url, SourceRelativeUrl, SourceFileName - | join kind=leftanti ( - OfficeActivity - | where TimeGenerated between (ago(starttime) .. ago(endtime)) - | where Operation =~ uploadOp - | where SourceFileExtension has_any (execExt) - | summarize SourceRelativeUrl = make_set(SourceRelativeUrl, 100000), UserId = make_set(UserId, 100000), PrevSeenCount = count() by SourceFileName - // Uncomment the line below to enforce the threshold - //| where PrevSeenCount > threshold - | mvexpand SourceRelativeUrl, UserId - | extend SourceRelativeUrl = tostring(SourceRelativeUrl), UserId = tostring(UserId) // Ensure consistent types for SourceRelativeUrl and UserId - ) on SourceFileName, SourceRelativeUrl, UserId - | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2]) - | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\.', '_')) - | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false) - | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), UserAgents = make_list(UserAgent, 100000), OfficeIds = make_list(OfficeId, 100000), SourceRelativeUrls = make_list(SourceRelativeUrl, 100000), FileNames = make_list(tostring(SourceFileName), 100000) // Cast FileNames to string - by OfficeWorkload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // EnrichedMicrosoft365AuditLogs Query - let EnrichedEvents = EnrichedMicrosoft365AuditLogs - | where TimeGenerated >= ago(endtime) - | where Operation == uploadOp - | extend SourceFileExtension = extract(@"\.([^\./]+)$", 1, tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)) // Extract file extension - | where SourceFileExtension in (execExt) - | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) - | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl) - | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName) - | extend RecordType = coalesce(tostring(RecordType), "UnknownRecordType") // Ensure RecordType is a string - | project TimeGenerated, Id, Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent), Site_Url, SourceRelativeUrl, SourceFileName - | join kind=leftanti ( - EnrichedMicrosoft365AuditLogs - | where TimeGenerated between (ago(starttime) .. ago(endtime)) - | where Operation == uploadOp - | extend SourceFileExtension = extract(@"\.([^\./]+)$", 1, tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)) // Extract file extension - | where SourceFileExtension in (execExt) - | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl) - | summarize SourceRelativeUrl = make_set(SourceRelativeUrl, 100000), UserId = make_set(UserId, 100000), PrevSeenCount = count() by SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName) - // Uncomment the line below to enforce the threshold - //| where PrevSeenCount > threshold - | mvexpand SourceRelativeUrl, UserId - | extend SourceRelativeUrl = tostring(SourceRelativeUrl), UserId = tostring(UserId) // Ensure consistent types for SourceRelativeUrl and UserId - ) on SourceFileName, SourceRelativeUrl, UserId - | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2]) - | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\.', '_')) - | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false) - | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), UserAgents = make_list(UserAgent, 100000), Ids = make_list(Id, 100000), SourceRelativeUrls = make_list(tostring(SourceRelativeUrl), 100000), FileNames = make_list(tostring(SourceFileName), 100000) // Cast FileNames to string - by Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // Combine Office and Enriched Logs - let CombinedEvents = EnrichedEvents - | union isfuzzy=true OfficeEvents - | summarize arg_min(StartTime, *) by tostring(FileNames), UserId, Site_Url, tostring(RecordType) // Ensure FileNames and RecordType are strings - | order by StartTime desc; - CombinedEvents - | project StartTime, EndTime, Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder, FileNames, UserAgents, AccountName, AccountUPNSuffix; + // Set query parameters + let threshold = 2; + let uploadOp = 'FileUploaded'; + // Extensions that are interesting. Add/Remove to this list as you see fit + let execExt = dynamic(['exe', 'inf', 'gzip', 'cmd', 'bat']); + let starttime = 8d; + let endtime = 1d; + + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where TimeGenerated >= ago(endtime) + | where Operation =~ uploadOp + | where SourceFileExtension has_any (execExt) + | extend RecordType = coalesce(tostring(RecordType), "UnknownRecordType") // Ensure RecordType is a string + | project TimeGenerated, OfficeId, OfficeWorkload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, UserAgent, Site_Url, SourceRelativeUrl, SourceFileName + | join kind=leftanti ( + OfficeActivity + | where TimeGenerated between (ago(starttime) .. ago(endtime)) + | where Operation =~ uploadOp + | where SourceFileExtension has_any (execExt) + | summarize SourceRelativeUrl = make_set(SourceRelativeUrl, 100000), UserId = make_set(UserId, 100000), PrevSeenCount = count() by SourceFileName + // Uncomment the line below to enforce the threshold + //| where PrevSeenCount > threshold + | mvexpand SourceRelativeUrl, UserId + | extend SourceRelativeUrl = tostring(SourceRelativeUrl), UserId = tostring(UserId) // Ensure consistent types for SourceRelativeUrl and UserId + ) on SourceFileName, SourceRelativeUrl, UserId + | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2]) + | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\.', '_')) + | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false) + | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), UserAgents = make_list(UserAgent, 100000), OfficeIds = make_list(OfficeId, 100000), SourceRelativeUrls = make_list(SourceRelativeUrl, 100000), FileNames = make_list(tostring(SourceFileName), 100000) // Cast FileNames to string + by OfficeWorkload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where TimeGenerated >= ago(endtime) + | where Operation == uploadOp + | extend SourceFileExtension = extract(@"\.([^\./]+)$", 1, tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)) // Extract file extension + | where SourceFileExtension in (execExt) + | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) + | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl) + | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName) + | extend RecordType = coalesce(tostring(RecordType), "UnknownRecordType") // Ensure RecordType is a string + | project TimeGenerated, Id, Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent), Site_Url, SourceRelativeUrl, SourceFileName + | join kind=leftanti ( + EnrichedMicrosoft365AuditLogs + | where TimeGenerated between (ago(starttime) .. ago(endtime)) + | where Operation == uploadOp + | extend SourceFileExtension = extract(@"\.([^\./]+)$", 1, tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)) // Extract file extension + | where SourceFileExtension in (execExt) + | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl) + | summarize SourceRelativeUrl = make_set(SourceRelativeUrl, 100000), UserId = make_set(UserId, 100000), PrevSeenCount = count() by SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName) + // Uncomment the line below to enforce the threshold + //| where PrevSeenCount > threshold + | mvexpand SourceRelativeUrl, UserId + | extend SourceRelativeUrl = tostring(SourceRelativeUrl), UserId = tostring(UserId) // Ensure consistent types for SourceRelativeUrl and UserId + ) on SourceFileName, SourceRelativeUrl, UserId + | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2]) + | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\.', '_')) + | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false) + | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), UserAgents = make_list(UserAgent, 100000), Ids = make_list(Id, 100000), SourceRelativeUrls = make_list(tostring(SourceRelativeUrl), 100000), FileNames = make_list(tostring(SourceFileName), 100000) // Cast FileNames to string + by Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + + // Combine Office and Enriched Logs + let CombinedEvents = EnrichedEvents + | union isfuzzy=true OfficeEvents + | summarize arg_min(StartTime, *) by tostring(FileNames), UserId, Site_Url, tostring(RecordType) // Ensure FileNames and RecordType are strings + | order by StartTime desc; + + // Final Output + CombinedEvents + | project StartTime, EndTime, Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder, FileNames, UserAgents, AccountName, AccountUPNSuffix; entityMappings: - entityType: Account fieldMappings: @@ -101,7 +107,7 @@ entityMappings: - entityType: IP fieldMappings: - identifier: Address - columnName: ClientIp + columnName: ClientIP - entityType: URL fieldMappings: - identifier: Url diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - RareOfficeOperations.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - RareOfficeOperations.yaml index 1e483ccf78..54d0316344 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - RareOfficeOperations.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - RareOfficeOperations.yaml @@ -22,22 +22,29 @@ query: | // OfficeActivity Query let OfficeEvents = OfficeActivity | where Operation in~ ( "Add-MailboxPermission", "Add-MailboxFolderPermission", "Set-Mailbox", "New-ManagementRoleAssignment", "New-InboxRule", "Set-InboxRule", "Set-TransportRule") - and not(UserId has_any ('NT AUTHORITY\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\SYSTEM (Microsoft.Exchange.AdminApi.NetCore)', 'NT AUTHORITY\\SYSTEM (w3wp)', 'devilfish-applicationaccount') and Operation in~ ( "Add-MailboxPermission", "Set-Mailbox")) + and not(UserId has_any ('NT AUTHORITY\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\SYSTEM (Microsoft.Exchange.AdminApi.NetCore)', 'NT AUTHORITY\\SYSTEM (w3wp)', 'devilfish-applicationaccount') + and Operation in~ ( "Add-MailboxPermission", "Set-Mailbox")) | extend ClientIPOnly = tostring(extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?', dynamic(["IPAddress"]), ClientIP)[0]) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + | extend AccountName = tostring(split(UserId, "@")[0]), + AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // EnrichedMicrosoft365AuditLogs Query let EnrichedEvents = EnrichedMicrosoft365AuditLogs | where Operation in~ ( "Add-MailboxPermission", "Add-MailboxFolderPermission", "Set-Mailbox", "New-ManagementRoleAssignment", "New-InboxRule", "Set-InboxRule", "Set-TransportRule") - and not(UserId has_any ('NT AUTHORITY\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\SYSTEM (Microsoft.Exchange.AdminApi.NetCore)', 'NT AUTHORITY\\SYSTEM (w3wp)', 'devilfish-applicationaccount') and Operation in~ ( "Add-MailboxPermission", "Set-Mailbox")) + and not(UserId has_any ('NT AUTHORITY\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\SYSTEM (Microsoft.Exchange.AdminApi.NetCore)', 'NT AUTHORITY\\SYSTEM (w3wp)', 'devilfish-applicationaccount') + and Operation in~ ( "Add-MailboxPermission", "Set-Mailbox")) | extend ClientIPOnly = tostring(extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?', dynamic(["IPAddress"]), ClientIp)[0]) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + | extend AccountName = tostring(split(UserId, "@")[0]), + AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine and Deduplicate Office and Enriched Logs let CombinedEvents = OfficeEvents | union EnrichedEvents | summarize arg_min(TimeGenerated, *) by Operation, UserId, ClientIPOnly; + // Final Output CombinedEvents - | project TimeGenerated, Operation, UserId, AccountName, AccountUPNSuffix, ClientIPOnly + | project TimeGenerated, Operation, UserId, AccountName, AccountUPNSuffix, ClientIPOnly entityMappings: - entityType: Account fieldMappings: @@ -51,9 +58,5 @@ entityMappings: fieldMappings: - identifier: Address columnName: ClientIPOnly - - entityType: CloudApplication - fieldMappings: - - identifier: AppId - columnName: AppId version: 2.0.7 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewIP.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewIP.yaml index c5773d2077..b5b02f5145 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewIP.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewIP.yaml @@ -22,6 +22,7 @@ query: | let szOperations = dynamic(["FileDownloaded", "FileUploaded"]); let starttime = 14d; let endtime = 1d; + // Define a baseline of normal user behavior for OfficeActivity let userBaselineOffice = OfficeActivity | where TimeGenerated between(ago(starttime)..ago(endtime)) @@ -30,6 +31,7 @@ query: | | where isnotempty(UserAgent) | summarize Count = count() by UserId, Operation, Site_Url, ClientIP | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url, ClientIP; + // Get recent user activity for OfficeActivity let recentUserActivityOffice = OfficeActivity | where TimeGenerated > ago(endtime) @@ -37,6 +39,7 @@ query: | | where Operation in~ (szOperations) | where isnotempty(UserAgent) | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), RecentCount = count() by UserId, UserType, Operation, Site_Url, ClientIP, OfficeObjectId, OfficeWorkload, UserAgent; + // Define a baseline of normal user behavior for EnrichedMicrosoft365AuditLogs let userBaselineEnriched = EnrichedMicrosoft365AuditLogs | where TimeGenerated between(ago(starttime)..ago(endtime)) @@ -47,6 +50,7 @@ query: | | where isnotempty(UserAgent) | summarize Count = count() by UserId, Operation, Site_Url, ClientIp | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url, ClientIp; + // Get recent user activity for EnrichedMicrosoft365AuditLogs let recentUserActivityEnriched = EnrichedMicrosoft365AuditLogs | where TimeGenerated > ago(endtime) @@ -56,15 +60,17 @@ query: | | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) | where isnotempty(UserAgent) | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), RecentCount = count() by UserId, UserType, Operation, Site_Url, ClientIp, ObjectId, Workload, UserAgent; + // Combine user baselines and recent activity, calculate deviation, and deduplicate let UserBehaviorAnalysis = userBaselineOffice | join kind=inner (recentUserActivityOffice) on UserId, Operation, Site_Url, ClientIP | union (userBaselineEnriched | join kind=inner (recentUserActivityEnriched) on UserId, Operation, Site_Url, ClientIp) | extend Deviation = abs(RecentCount - AvgCount) / AvgCount; + // Filter for significant deviations UserBehaviorAnalysis | where Deviation > threshold - | project StartTimeUtc, EndTimeUtc, UserId, UserType, Operation, ClientIP, Site_Url, ObjectId, OfficeObjectId, OfficeWorkload, Workload, UserAgent, Deviation, Count=RecentCount + | project StartTimeUtc, EndTimeUtc, UserId, UserType, Operation, ClientIP, Site_Url, ObjectId, OfficeObjectId, OfficeWorkload, Workload, UserAgent, Deviation, Count = RecentCount | order by Count desc, ClientIP asc, Operation asc, UserId asc | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); entityMappings: @@ -79,7 +85,7 @@ entityMappings: - entityType: IP fieldMappings: - identifier: Address - columnName: ClientIp + columnName: ClientIP - entityType: URL fieldMappings: - identifier: Url diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml index ea908dd604..94d24a64dd 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml @@ -17,14 +17,14 @@ tactics: relevantTechniques: - T1030 query: | - let threshold = 5; + let threshold = 5; let szSharePointFileOperation = "SharePointFileOperation"; let szOperations = dynamic(["FileDownloaded", "FileUploaded"]); let starttime = 14d; let endtime = 1d; // OfficeActivity - Base Events let BaseeventsOffice = OfficeActivity - | where TimeGenerated between (ago(starttime) .. ago(endtime)) + | where TimeGenerated between (ago(starttime)..ago(endtime)) | where RecordType =~ szSharePointFileOperation | where Operation in~ (szOperations) | where isnotempty(UserAgent); @@ -44,11 +44,11 @@ query: | | where Operation in~ (szOperations) | where isnotempty(UserAgent) | where UserAgent in~ (FrequentUAOffice) - | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), OfficeObjectIdCount = dcount(OfficeObjectId), OfficeObjectIdList = make_set(OfficeObjectId), UserAgentSeenCount = count() - by RecordType, Operation, UserAgent, UserType, UserId, ClientIP, OfficeWorkload, Site_Url; + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OfficeObjectIdCount = dcount(OfficeObjectId), OfficeObjectIdList = make_set(OfficeObjectId), UserAgentSeenCount = count() + by RecordType, Operation, UserAgent, UserType, UserId, ClientIP, OfficeWorkload, Site_Url; // EnrichedMicrosoft365AuditLogs - Base Events let BaseeventsEnriched = EnrichedMicrosoft365AuditLogs - | where TimeGenerated between (ago(starttime) .. ago(endtime)) + | where TimeGenerated between (ago(starttime)..ago(endtime)) | where RecordType == szSharePointFileOperation | where Operation in (szOperations) | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) @@ -72,8 +72,8 @@ query: | | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) | where isnotempty(UserAgent) | where UserAgent in (FrequentUAEnriched) - | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), ObjectIdCount = dcount(ObjectId), ObjectIdList = make_set(ObjectId), UserAgentSeenCount = count() - by RecordType, Operation, UserAgent, UserId, ClientIp, Site_Url; + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ObjectIdCount = dcount(ObjectId), ObjectIdList = make_set(ObjectId), UserAgentSeenCount = count() + by RecordType, Operation, UserAgent, UserId, ClientIp, Site_Url; // Combine Baseline and Recent Activity, Calculate Deviation, and Deduplicate let UserBehaviorAnalysisOffice = UserBaseLineOffice | join kind=inner (RecentActivityOffice) on UserId, Operation, Site_Url @@ -87,7 +87,8 @@ query: | // Filter and Format Final Results CombinedUserBehaviorAnalysis | where Deviation > 0.25 - | extend UserIdName = tostring(split(UserId, '@')[0]), UserIdUPNSuffix = tostring(split(UserId, '@')[1]) + | extend UserIdName = tostring(split(UserId, '@')[0]), + UserIdUPNSuffix = tostring(split(UserId, '@')[1]) | project-reorder StartTime, EndTime, UserAgent, UserAgentSeenCount, UserId, ClientIP, Site_Url | order by UserAgentSeenCount desc, UserAgent asc, UserId asc, Site_Url asc entityMappings: @@ -102,7 +103,7 @@ entityMappings: - entityType: IP fieldMappings: - identifier: Address - columnName: ClientIp + columnName: ClientIP - entityType: URL fieldMappings: - identifier: Url diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - exchange_auditlogdisabled.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - exchange_auditlogdisabled.yaml index 7d21329a56..9ac9b79f19 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - exchange_auditlogdisabled.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - exchange_auditlogdisabled.yaml @@ -1,7 +1,7 @@ id: dc451755-8ab3-4059-b805-e454c45d1d44 name: GSA Enriched Office 365 - Exchange AuditLog Disabled description: | - 'Identifies when the exchange audit logging has been disabled which may be an adversary attempt to evade detection or avoid other defenses.' + Identifies when the Exchange audit logging has been disabled, which may indicate an adversary attempt to evade detection or bypass other defenses. severity: Medium status: Available requiredDataConnectors: @@ -17,37 +17,41 @@ tactics: relevantTechniques: - T1562 query: | - // OfficeActivity Query - let OfficeEvents = OfficeActivity - | where OfficeWorkload =~ "Exchange" - | where UserType in~ ("Admin", "DcAdmin") - // Only admin or global-admin can disable audit logging - | where Operation =~ "Set-AdminAuditLogConfig" - | extend AdminAuditLogEnabledValue = tostring(parse_json(tostring(parse_json(tostring(array_slice(parse_json(Parameters), 3, 3)))[0])).Value) - | where AdminAuditLogEnabledValue =~ "False" - | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() by Operation, UserType, UserId, ClientIP, ResultStatus, Parameters, AdminAuditLogEnabledValue - | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), UserId) - | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '') - | extend AccountName = iff(UserId contains '\\', tostring(split(UserId, '\\')[1]), AccountName) - | extend AccountNTDomain = iff(UserId contains '\\', tostring(split(UserId, '\\')[0]), ''); - // EnrichedMicrosoft365AuditLogs Query - let EnrichedEvents = EnrichedMicrosoft365AuditLogs - | where Workload =~ "Exchange" - | where UserType in~ ("Admin", "DcAdmin") - | where Operation =~ "Set-AdminAuditLogConfig" - | extend AdminAuditLogEnabledValue = tostring(parse_json(tostring(parse_json(tostring(array_slice(parse_json(tostring(AdditionalProperties.Parameters)), 3, 3)))[0])).Value) - | where AdminAuditLogEnabledValue =~ "False" - | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() by Operation, UserType, UserId, ClientIP = SourceIp, ResultStatus, Parameters = tostring(AdditionalProperties.Parameters), AdminAuditLogEnabledValue - | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), UserId) - | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '') - | extend AccountName = iff(UserId contains '\\', tostring(split(UserId, '\\')[1]), AccountName) - | extend AccountNTDomain = iff(UserId contains '\\', tostring(split(UserId, '\\')[0]), ''); - // Combine Office and Enriched Events and Deduplicate - let CombinedEvents = OfficeEvents - | union EnrichedEvents - | summarize arg_min(StartTimeUtc, *) by Operation, UserId, ClientIP; - // Project Final Output - CombinedEvents + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where OfficeWorkload =~ "Exchange" + | where UserType in~ ("Admin", "DcAdmin") + // Only admin or global-admin can disable audit logging + | where Operation =~ "Set-AdminAuditLogConfig" + | extend ParsedParameters = parse_json(Parameters) + | extend AdminAuditLogEnabledValue = tostring(ParsedParameters[3].Value) + | where AdminAuditLogEnabledValue =~ "False" + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() + by Operation, UserType, UserId, ClientIP, ResultStatus, Parameters, AdminAuditLogEnabledValue + | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), + iff(UserId contains '\\', tostring(split(UserId, '\\')[1]), UserId)) + | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '') + | extend AccountNTDomain = iff(UserId contains '\\', tostring(split(UserId, '\\')[0]), ''); + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload =~ "Exchange" + | where UserType in~ ("Admin", "DcAdmin") + | where Operation =~ "Set-AdminAuditLogConfig" + | extend ParsedParameters = parse_json(AdditionalProperties.Parameters) + | extend AdminAuditLogEnabledValue = tostring(ParsedParameters[3].Value) + | where AdminAuditLogEnabledValue =~ "False" + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() + by Operation, UserType, UserId, ClientIP = SourceIp, ResultStatus, Parameters = tostring(AdditionalProperties.Parameters), AdminAuditLogEnabledValue + | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), + iff(UserId contains '\\', tostring(split(UserId, '\\')[1]), UserId)) + | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '') + | extend AccountNTDomain = iff(UserId contains '\\', tostring(split(UserId, '\\')[0]), ''); + // Combine Office and Enriched Events and Deduplicate + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(StartTimeUtc, *) by Operation, UserId, ClientIP; + // Project Final Output + CombinedEvents | project StartTimeUtc, EndTimeUtc, Operation, UserType, UserId, ClientIP, ResultStatus, Parameters, AdminAuditLogEnabledValue, AccountName, AccountUPNSuffix, AccountNTDomain entityMappings: - entityType: Account @@ -58,13 +62,9 @@ entityMappings: columnName: AccountName - identifier: UPNSuffix columnName: AccountUPNSuffix - - entityType: Account - fieldMappings: - - identifier: Name - columnName: AccountNTDomain - entityType: IP fieldMappings: - identifier: Address columnName: ClientIP version: 2.0.8 -kind: Scheduled \ No newline at end of file +kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - office_policytampering.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - office_policytampering.yaml index f07c58f8c8..74ca0aa0cb 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - office_policytampering.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - office_policytampering.yaml @@ -21,54 +21,69 @@ relevantTechniques: - T1098 - T1562 query: | - // Query for EnrichedMicrosoft365AuditLogs - let enrichedOpList = EnrichedMicrosoft365AuditLogs - | summarize by Operation - | where Operation has_any ("Remove", "Disable") - | where Operation contains "AntiPhish" or Operation contains "SafeAttachment" or Operation contains "SafeLinks" or Operation contains "Dlp" or Operation contains "Audit" - | summarize make_set(Operation, 500); - let enrichedLogs = EnrichedMicrosoft365AuditLogs - | where RecordType == "ExchangeAdmin" - | where UserType in~ ("Admin", "DcAdmin") - | where Operation in~ (enrichedOpList) - | extend ClientIPOnly = case( - ClientIp has ".", tostring(split(ClientIp, ":")[0]), - ClientIp has "[", tostring(trim_start(@'[[]', tostring(split(ClientIp, "]")[0]))), - ClientIp - ) - | extend Port = case( - ClientIp has ".", tostring(split(ClientIp, ":")[1]), - ClientIp has "[", tostring(split(ClientIp, "]:")[1]), - "" - ) - | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() by Operation, UserType, UserId, ClientIP = ClientIPOnly, Port, ResultStatus, Parameters = tostring(AdditionalProperties.Parameters) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // Query for OfficeActivity - let officeOpList = OfficeActivity + // Query for EnrichedMicrosoft365AuditLogs + let enrichedOpList = EnrichedMicrosoft365AuditLogs | summarize by Operation | where Operation has_any ("Remove", "Disable") - | where Operation contains "AntiPhish" or Operation contains "SafeAttachment" or Operation contains "SafeLinks" or Operation contains "Dlp" or Operation contains "Audit" + | where Operation contains "AntiPhish" + or Operation contains "SafeAttachment" + or Operation contains "SafeLinks" + or Operation contains "Dlp" + or Operation contains "Audit" | summarize make_set(Operation, 500); - let officeLogs = OfficeActivity + + let enrichedLogs = EnrichedMicrosoft365AuditLogs + | where RecordType == "ExchangeAdmin" + | where UserType in~ ("Admin", "DcAdmin") + | where Operation in~ (enrichedOpList) + | extend ClientIPOnly = case( + ClientIp has ".", tostring(split(ClientIp, ":")[0]), + ClientIp has "[", tostring(trim_start(@'[[]', tostring(split(ClientIp, "]")[0]))), + ClientIp + ) + | extend Port = case( + ClientIp has ".", tostring(split(ClientIp, ":")[1]), + ClientIp has "[", tostring(split(ClientIp, "]:")[1]), + "" + ) + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() + by Operation, UserType, UserId, ClientIP = ClientIPOnly, Port, ResultStatus, Parameters = tostring(AdditionalProperties.Parameters) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + + // Query for OfficeActivity + let officeOpList = OfficeActivity + | summarize by Operation + | where Operation has_any ("Remove", "Disable") + | where Operation contains "AntiPhish" + or Operation contains "SafeAttachment" + or Operation contains "SafeLinks" + or Operation contains "Dlp" + or Operation contains "Audit" + | summarize make_set(Operation, 500); + + let officeLogs = OfficeActivity | where RecordType =~ "ExchangeAdmin" | where UserType in~ ("Admin","DcAdmin") | where Operation in~ (officeOpList) | extend ClientIPOnly = case( ClientIP has ".", tostring(split(ClientIP,":")[0]), - ClientIP has "[", tostring(trim_start(@'[[]',tostring(split(ClientIP,"]")[0]))), + ClientIP has "[", tostring(trim_start(@'[[]', tostring(split(ClientIP,"]")[0]))), ClientIP ) | extend Port = case( ClientIP has ".", tostring(split(ClientIP,":")[1]), - ClientIP has "[", tostring(split(ClientIP,"]:")[1]), + ClientIP has "[", tostring(split(ClientIP, "]:")[1]), "" ) - | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() by Operation, UserType, UserId, ClientIP = ClientIPOnly, Port, ResultStatus, Parameters + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() + by Operation, UserType, UserId, ClientIP = ClientIPOnly, Port, ResultStatus, Parameters | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // Combine Enriched Logs and Office Activity Logs - union isfuzzy=true enrichedLogs, officeLogs - | summarize StartTimeUtc = min(StartTimeUtc), EndTimeUtc = max(EndTimeUtc), TotalOperationCount = sum(OperationCount) by Operation, UserType, UserId, ClientIP, Port, ResultStatus, Parameters, AccountName, AccountUPNSuffix - | order by StartTimeUtc desc; + + // Combine Enriched Logs and Office Activity Logs + union isfuzzy=true enrichedLogs, officeLogs + | summarize StartTimeUtc = min(StartTimeUtc), EndTimeUtc = max(EndTimeUtc), TotalOperationCount = sum(OperationCount) + by Operation, UserType, UserId, ClientIP, Port, ResultStatus, Parameters, AccountName, AccountUPNSuffix + | order by StartTimeUtc desc; entityMappings: - entityType: Account fieldMappings: diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml index 02dfc1c3b6..a1e0172802 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml @@ -4,6 +4,7 @@ description: | Identifies Office365 Sharepoint File Transfers above a certain threshold in a 15-minute time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur. severity: Medium +status: Available requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: @@ -23,27 +24,32 @@ query: | | where Workload has_any("SharePoint", "OneDrive") | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") | summarize count_distinct_ObjectId=dcount(ObjectId), fileslist=make_set(ObjectId, 10000) - by UserId, ClientIp, bin(TimeGenerated, 15m) + by UserId, ClientIP, bin(TimeGenerated, 15m) | where count_distinct_ObjectId >= threshold | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + | extend AccountName = tostring(split(UserId, "@")[0]), + AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // OfficeActivity Query let OfficeEvents = OfficeActivity | where EventSource == "SharePoint" | where OfficeWorkload has_any("SharePoint", "OneDrive") | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") | summarize count_distinct_OfficeObjectId=dcount(OfficeObjectId), fileslist=make_set(OfficeObjectId, 10000) - by UserId, ClientIP, bin(TimeGenerated, 15m) + by UserId, ClientIP, bin(TimeGenerated, 15m) | where count_distinct_OfficeObjectId >= threshold | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + | extend AccountName = tostring(split(UserId, "@")[0]), + AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine Office and Enriched Logs let CombinedEvents = OfficeEvents | union EnrichedEvents | summarize arg_min(TimeGenerated, *) by UserId, ClientIP; + // Final Output CombinedEvents - | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId + | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist | order by TimeGenerated desc entityMappings: - entityType: Account @@ -57,7 +63,7 @@ entityMappings: - entityType: IP fieldMappings: - identifier: Address - columnName: ClientIp + columnName: ClientIP - entityType: File fieldMappings: - identifier: Name @@ -76,5 +82,5 @@ incidentConfiguration: - Account groupByAlertDetails: [] groupByCustomDetails: [] -version: 1.0.6 +version: 2.0.6 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml index e5bbb44ee0..a8541ad738 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml @@ -4,6 +4,7 @@ description: | Identifies Office365 Sharepoint File Transfers with a distinct folder count above a certain threshold in a 15-minute time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur. severity: Medium +status: Available requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: @@ -17,36 +18,42 @@ tactics: relevantTechniques: - T1020 query: | - let threshold = 500; - // OfficeActivity Query - let OfficeEvents = OfficeActivity - | where EventSource == "SharePoint" - | where OfficeWorkload has_any("SharePoint", "OneDrive") - | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | summarize count_distinct_SourceRelativeUrl = dcount(SourceRelativeUrl), dirlist = make_set(SourceRelativeUrl, 10000) - by UserId, ClientIP, UserAgent, bin(TimeGenerated, 15m) - | where count_distinct_SourceRelativeUrl >= threshold - | extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField", "_", tostring(hash(tostring(dirlist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // EnrichedMicrosoft365AuditLogs Query - let EnrichedEvents = EnrichedMicrosoft365AuditLogs - | where Workload has_any("SharePoint", "OneDrive") - | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | extend EventSource = tostring(parse_json(tostring(AdditionalProperties)).EventSource) - | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) - | summarize count_distinct_ObjectId = dcount(ObjectId), dirlist = make_set(ObjectId, 10000) + let threshold = 500; + + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where EventSource == "SharePoint" + | where OfficeWorkload has_any("SharePoint", "OneDrive") + | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | summarize count_distinct_SourceRelativeUrl = dcount(SourceRelativeUrl), dirlist = make_set(SourceRelativeUrl, 10000) by UserId, ClientIp, UserAgent, bin(TimeGenerated, 15m) - | where count_distinct_ObjectId >= threshold - | extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField", "_", tostring(hash(tostring(dirlist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // Combine Office and Enriched Logs - let CombinedEvents = OfficeEvents - | union EnrichedEvents - | summarize arg_min(TimeGenerated, *) by UserId, ClientIP, UserAgent; - // Final Output - CombinedEvents - | project TimeGenerated, UserId, ClientIP, UserAgent, AccountName, AccountUPNSuffix, DirSample, count_distinct_SourceRelativeUrl - | order by TimeGenerated desc + | where count_distinct_SourceRelativeUrl >= threshold + | extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField", "_", tostring(hash(tostring(dirlist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), + AccountUPNSuffix = tostring(split(UserId, "@")[1]); + + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload has_any("SharePoint", "OneDrive") + | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | extend EventSource = tostring(parse_json(tostring(AdditionalProperties)).EventSource) + | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) + | summarize count_distinct_ObjectId = dcount(ObjectId), dirlist = make_set(ObjectId, 10000) + by UserId, ClientIp, UserAgent, bin(TimeGenerated, 15m) + | where count_distinct_ObjectId >= threshold + | extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField", "_", tostring(hash(tostring(dirlist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), + AccountUPNSuffix = tostring(split(UserId, "@")[1]); + + // Combine Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(TimeGenerated, *) by UserId, ClientIp, UserAgent; + + // Final Output + CombinedEvents + | project TimeGenerated, UserId, ClientIp, AccountName, AccountUPNSuffix, DirSample, count_distinct_SourceRelativeUrl, count_distinct_ObjectId, dirlist + | order by TimeGenerated desc entityMappings: - entityType: Account fieldMappings: @@ -78,5 +85,5 @@ incidentConfiguration: - Account groupByAlertDetails: [] groupByCustomDetails: [] -version: 1.0.6 +version: 2.0.7 kind: Scheduled From 17582eeac9546ecc0400dfb7fee58f848abd3fca Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:20:56 +0300 Subject: [PATCH 03/27] kql validation fixes --- ... Mail_redirect_via_ExO_transport_rule.yaml | 98 ++++++------ ...- SharePoint_Downloads_byNewUserAgent.yaml | 145 +++++++---------- ...repoint_file_transfer_above_threshold.yaml | 11 +- ...file_transfer_folders_above_threshold.yaml | 151 +++++++++++------- 4 files changed, 206 insertions(+), 199 deletions(-) diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml index 8927e62141..c22cb23c12 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml @@ -1,63 +1,59 @@ -id: edcfc2e0-3134-434c-8074-9101c530d419 -name: GSA Enriched Office 365 - Mail redirect via ExO transport rule +id: dc451755-8ab3-4059-b805-e454c45d1d44 +name: GSA Enriched Office 365 - Exchange AuditLog Disabled description: | - Identifies when Exchange Online transport rule configured to forward emails. - This could be an adversary mailbox configured to collect mail from multiple user accounts. + Identifies when the Exchange audit logging has been disabled, which may indicate an adversary attempt to evade detection or bypass other defenses. severity: Medium -status: Available +status: Available requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs -queryFrequency: 1h -queryPeriod: 1h +queryFrequency: 1d +queryPeriod: 1d triggerOperator: gt triggerThreshold: 0 tactics: - - Collection - - Exfiltration + - DefenseEvasion relevantTechniques: - - T1114 - - T1020 + - T1562 query: | - let officeActivityQuery = OfficeActivity - | where OfficeWorkload == "Exchange" - | where Operation in~ ("New-TransportRule", "Set-TransportRule") - | mv-apply DynamicParameters = todynamic(Parameters) on (summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))) - | extend RuleName = case( - Operation =~ "Set-TransportRule", OfficeObjectId, - Operation =~ "New-TransportRule", ParsedParameters.Name, - "Unknown") - | mv-expand ExpandedParameters = todynamic(Parameters) - | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) - | extend RedirectTo = ExpandedParameters.Value - | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] - | extend From = ParsedParameters.From - | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - let enrichedLogsQuery = EnrichedMicrosoft365AuditLogs - | where Workload == "Exchange" - | where Operation in~ ("New-TransportRule", "Set-TransportRule") - | mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on ( - summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) - ) - | extend RuleName = case( - Operation =~ "Set-TransportRule", ObjectId, // Assuming ObjectId maps to OfficeObjectId - Operation =~ "New-TransportRule", ParsedParameters.Name, - "Unknown" - ) - | mv-expand ExpandedParameters = todynamic(tostring(AdditionalProperties.Parameters)) - | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) - | extend RedirectTo = ExpandedParameters.Value - | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] - | extend From = ParsedParameters.From - | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters = tostring(AdditionalProperties.Parameters) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // Combine both queries - union isfuzzy=true officeActivityQuery, enrichedLogsQuery - | summarize arg_min(TimeGenerated, *) by RuleName, RedirectTo - | project TimeGenerated, RedirectTo, IPAddress, Port, UserId, From, Operation, RuleName, Parameters, AccountName, AccountUPNSuffix - | order by TimeGenerated desc; + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where OfficeWorkload =~ "Exchange" + | where UserType in~ ("Admin", "DcAdmin") + // Only admin or global-admin can disable audit logging + | where Operation =~ "Set-AdminAuditLogConfig" + | extend ParsedParameters = parse_json(Parameters) + | extend AdminAuditLogEnabledValue = tostring(ParsedParameters[3].Value) + | where AdminAuditLogEnabledValue =~ "False" + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() + by Operation, UserType, UserId, ClientIP, ResultStatus, Parameters, AdminAuditLogEnabledValue + | extend AccountName = iff(UserId contains '@', tostring(split(UserId, "@")[0]), + iff(UserId contains '\\', tostring(split(UserId, "\\")[1]), UserId)) + | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, "@")[1]), '') + | extend AccountNTDomain = iff(UserId contains '\\', tostring(split(UserId, "\\")[0]), ''); + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload =~ "Exchange" + | where UserType in~ ("Admin", "DcAdmin") + | where Operation =~ "Set-AdminAuditLogConfig" + | extend ParsedParameters = parse_json(AdditionalProperties.Parameters) + | extend AdminAuditLogEnabledValue = tostring(ParsedParameters[3].Value) + | where AdminAuditLogEnabledValue =~ "False" + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() + by Operation, UserType, UserId, ClientIP = SourceIp, ResultStatus, Parameters = tostring(AdditionalProperties.Parameters), AdminAuditLogEnabledValue + | extend AccountName = iff(UserId contains '@', tostring(split(UserId, "@")[0]), + iff(UserId contains '\\', tostring(split(UserId, "\\")[1]), UserId)) + | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, "@")[1]), '') + | extend AccountNTDomain = iff(UserId contains '\\', tostring(split(UserId, "\\")[0]), ''); + // Combine Office and Enriched Events and Deduplicate + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(StartTimeUtc, *) by Operation, UserId, ClientIP; + // Project Final Output + CombinedEvents + | project StartTimeUtc, EndTimeUtc, Operation, UserType, UserId, ClientIP, ResultStatus, Parameters, AdminAuditLogEnabledValue, AccountName, AccountUPNSuffix, AccountNTDomain + | order by StartTimeUtc desc entityMappings: - entityType: Account fieldMappings: @@ -70,6 +66,6 @@ entityMappings: - entityType: IP fieldMappings: - identifier: Address - columnName: IPAddress -version: 2.1.4 + columnName: ClientIP +version: 2.0.8 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml index 94d24a64dd..28cacf36af 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml @@ -1,112 +1,87 @@ -id: efd17c5f-5167-40f8-a1e9-0818940785d9 -name: GSA Enriched Office 365 - SharePointFileOperation via devices with previously unseen user agents +id: 30375d00-68cc-4f95-b89a-68064d566358 +name: GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold description: | - Identifies anomalies if the number of documents uploaded or downloaded from device(s) associated with a previously unseen user agent exceeds a threshold (default is 5) and deviation (default is 25%). + Identifies Office365 Sharepoint File Transfers above a certain threshold in a 15-minute time period. + Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur. severity: Medium status: Available requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs -queryFrequency: 1d -queryPeriod: 14d +queryFrequency: 15m +queryPeriod: 15m triggerOperator: gt triggerThreshold: 0 tactics: - Exfiltration relevantTechniques: - - T1030 + - T1020 query: | - let threshold = 5; - let szSharePointFileOperation = "SharePointFileOperation"; - let szOperations = dynamic(["FileDownloaded", "FileUploaded"]); - let starttime = 14d; - let endtime = 1d; - // OfficeActivity - Base Events - let BaseeventsOffice = OfficeActivity - | where TimeGenerated between (ago(starttime)..ago(endtime)) - | where RecordType =~ szSharePointFileOperation - | where Operation in~ (szOperations) - | where isnotempty(UserAgent); - // OfficeActivity - Frequent User Agents - let FrequentUAOffice = BaseeventsOffice - | summarize FUACount = count() by UserAgent, RecordType, Operation - | where FUACount >= threshold - | distinct UserAgent; - // OfficeActivity - User Baseline - let UserBaseLineOffice = BaseeventsOffice - | summarize Count = count() by UserId, Operation, Site_Url - | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url; - // OfficeActivity - Recent User Activity - let RecentActivityOffice = OfficeActivity - | where TimeGenerated > ago(endtime) - | where RecordType =~ szSharePointFileOperation - | where Operation in~ (szOperations) - | where isnotempty(UserAgent) - | where UserAgent in~ (FrequentUAOffice) - | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OfficeObjectIdCount = dcount(OfficeObjectId), OfficeObjectIdList = make_set(OfficeObjectId), UserAgentSeenCount = count() - by RecordType, Operation, UserAgent, UserType, UserId, ClientIP, OfficeWorkload, Site_Url; - // EnrichedMicrosoft365AuditLogs - Base Events - let BaseeventsEnriched = EnrichedMicrosoft365AuditLogs - | where TimeGenerated between (ago(starttime)..ago(endtime)) - | where RecordType == szSharePointFileOperation - | where Operation in (szOperations) - | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) - | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) - | where isnotempty(UserAgent); - // EnrichedMicrosoft365AuditLogs - Frequent User Agents - let FrequentUAEnriched = BaseeventsEnriched - | summarize FUACount = count() by UserAgent, RecordType, Operation - | where FUACount >= threshold - | distinct UserAgent; - // EnrichedMicrosoft365AuditLogs - User Baseline - let UserBaseLineEnriched = BaseeventsEnriched - | summarize Count = count() by UserId, Operation, Site_Url - | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url; - // EnrichedMicrosoft365AuditLogs - Recent User Activity - let RecentActivityEnriched = EnrichedMicrosoft365AuditLogs - | where TimeGenerated > ago(endtime) - | where RecordType == szSharePointFileOperation - | where Operation in (szOperations) - | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) - | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) - | where isnotempty(UserAgent) - | where UserAgent in (FrequentUAEnriched) - | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ObjectIdCount = dcount(ObjectId), ObjectIdList = make_set(ObjectId), UserAgentSeenCount = count() - by RecordType, Operation, UserAgent, UserId, ClientIp, Site_Url; - // Combine Baseline and Recent Activity, Calculate Deviation, and Deduplicate - let UserBehaviorAnalysisOffice = UserBaseLineOffice - | join kind=inner (RecentActivityOffice) on UserId, Operation, Site_Url - | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount; - let UserBehaviorAnalysisEnriched = UserBaseLineEnriched - | join kind=inner (RecentActivityEnriched) on UserId, Operation, Site_Url - | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount; + let threshold = 5000; + + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload has_any("SharePoint", "OneDrive") + | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | summarize count_distinct_ObjectId = dcount(ObjectId), fileslist = make_set(ObjectId, 10000) + by UserId, ClientIP, bin(TimeGenerated, 15m) + | where count_distinct_ObjectId >= threshold + | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), + AccountUPNSuffix = tostring(split(UserId, "@")[1]); + + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where EventSource == "SharePoint" + | where OfficeWorkload has_any("SharePoint", "OneDrive") + | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | summarize count_distinct_OfficeObjectId = dcount(OfficeObjectId), fileslist = make_set(OfficeObjectId, 10000) + by UserId, ClientIP, bin(TimeGenerated, 15m) + | where count_distinct_OfficeObjectId >= threshold + | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), + AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine Office and Enriched Logs - let CombinedUserBehaviorAnalysis = UserBehaviorAnalysisOffice - | union UserBehaviorAnalysisEnriched; - // Filter and Format Final Results - CombinedUserBehaviorAnalysis - | where Deviation > 0.25 - | extend UserIdName = tostring(split(UserId, '@')[0]), - UserIdUPNSuffix = tostring(split(UserId, '@')[1]) - | project-reorder StartTime, EndTime, UserAgent, UserAgentSeenCount, UserId, ClientIP, Site_Url - | order by UserAgentSeenCount desc, UserAgent asc, UserId asc, Site_Url asc + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(TimeGenerated, *) by UserId, ClientIP; + + // Final Output + CombinedEvents + | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist + | order by TimeGenerated desc entityMappings: - entityType: Account fieldMappings: - identifier: FullName columnName: UserId - identifier: Name - columnName: UserIdName + columnName: AccountName - identifier: UPNSuffix - columnName: UserIdUPNSuffix + columnName: AccountUPNSuffix - entityType: IP fieldMappings: - identifier: Address columnName: ClientIP - - entityType: URL + - entityType: File fieldMappings: - - identifier: Url - columnName: Site_Url -version: 2.2.6 + - identifier: Name + columnName: FileSample +customDetails: + TransferCount: count_distinct_ObjectId + FilesList: fileslist +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + groupByAlertDetails: [] + groupByCustomDetails: [] +version: 2.0.6 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml index a1e0172802..2a3ac82eaa 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml @@ -19,12 +19,13 @@ relevantTechniques: - T1020 query: | let threshold = 5000; + // EnrichedMicrosoft365AuditLogs Query let EnrichedEvents = EnrichedMicrosoft365AuditLogs | where Workload has_any("SharePoint", "OneDrive") | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") | summarize count_distinct_ObjectId=dcount(ObjectId), fileslist=make_set(ObjectId, 10000) - by UserId, ClientIP, bin(TimeGenerated, 15m) + by UserId, ClientIp, bin(TimeGenerated, 15m) | where count_distinct_ObjectId >= threshold | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) | extend AccountName = tostring(split(UserId, "@")[0]), @@ -36,7 +37,7 @@ query: | | where OfficeWorkload has_any("SharePoint", "OneDrive") | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") | summarize count_distinct_OfficeObjectId=dcount(OfficeObjectId), fileslist=make_set(OfficeObjectId, 10000) - by UserId, ClientIP, bin(TimeGenerated, 15m) + by UserId, ClientIp, bin(TimeGenerated, 15m) | where count_distinct_OfficeObjectId >= threshold | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) | extend AccountName = tostring(split(UserId, "@")[0]), @@ -45,11 +46,11 @@ query: | // Combine Office and Enriched Logs let CombinedEvents = OfficeEvents | union EnrichedEvents - | summarize arg_min(TimeGenerated, *) by UserId, ClientIP; + | summarize arg_min(TimeGenerated, *) by UserId, ClientIp; // Final Output CombinedEvents - | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist + | project TimeGenerated, UserId, ClientIp, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist | order by TimeGenerated desc entityMappings: - entityType: Account @@ -63,7 +64,7 @@ entityMappings: - entityType: IP fieldMappings: - identifier: Address - columnName: ClientIP + columnName: ClientIp - entityType: File fieldMappings: - identifier: Name diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml index a8541ad738..4508159a51 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml @@ -1,89 +1,124 @@ -id: abd6976d-8f71-4851-98c4-4d086201319c -name: GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold +id: efd17c5f-5167-40f8-a1e9-0818940785d9 +name: GSA Enriched Office 365 - SharePointFileOperation via devices with previously unseen user agents description: | - Identifies Office365 Sharepoint File Transfers with a distinct folder count above a certain threshold in a 15-minute time period. - Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur. + Identifies anomalies if the number of documents uploaded or downloaded from device(s) associated with a previously unseen user agent exceeds a threshold (default is 5) and deviation (default is 25%). severity: Medium status: Available requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs -queryFrequency: 15m -queryPeriod: 15m +queryFrequency: 1d +queryPeriod: 14d triggerOperator: gt triggerThreshold: 0 tactics: - Exfiltration relevantTechniques: - - T1020 + - T1030 query: | - let threshold = 500; + let threshold = 5; + let szSharePointFileOperation = "SharePointFileOperation"; + let szOperations = dynamic(["FileDownloaded", "FileUploaded"]); + let starttime = 14d; + let endtime = 1d; - // OfficeActivity Query - let OfficeEvents = OfficeActivity - | where EventSource == "SharePoint" - | where OfficeWorkload has_any("SharePoint", "OneDrive") - | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | summarize count_distinct_SourceRelativeUrl = dcount(SourceRelativeUrl), dirlist = make_set(SourceRelativeUrl, 10000) - by UserId, ClientIp, UserAgent, bin(TimeGenerated, 15m) - | where count_distinct_SourceRelativeUrl >= threshold - | extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField", "_", tostring(hash(tostring(dirlist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), - AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // OfficeActivity - Base Events + let BaseeventsOffice = OfficeActivity + | where TimeGenerated between (ago(starttime)..ago(endtime)) + | where RecordType =~ szSharePointFileOperation + | where Operation in~ (szOperations) + | where isnotempty(UserAgent); - // EnrichedMicrosoft365AuditLogs Query - let EnrichedEvents = EnrichedMicrosoft365AuditLogs - | where Workload has_any("SharePoint", "OneDrive") - | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | extend EventSource = tostring(parse_json(tostring(AdditionalProperties)).EventSource) + // OfficeActivity - Frequent User Agents + let FrequentUAOffice = BaseeventsOffice + | summarize FUACount = count() by UserAgent, RecordType, Operation + | where FUACount >= threshold + | distinct UserAgent; + + // OfficeActivity - User Baseline + let UserBaseLineOffice = BaseeventsOffice + | summarize Count = count() by UserId, Operation, Site_Url + | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url; + + // OfficeActivity - Recent User Activity + let RecentActivityOffice = OfficeActivity + | where TimeGenerated > ago(endtime) + | where RecordType =~ szSharePointFileOperation + | where Operation in~ (szOperations) + | where isnotempty(UserAgent) + | where UserAgent in~ (FrequentUAOffice) + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OfficeObjectIdCount = dcount(OfficeObjectId), OfficeObjectIdList = make_set(OfficeObjectId), UserAgentSeenCount = count() + by RecordType, Operation, UserAgent, UserType, UserId, ClientIP, OfficeWorkload, Site_Url; + + // EnrichedMicrosoft365AuditLogs - Base Events + let BaseeventsEnriched = EnrichedMicrosoft365AuditLogs + | where TimeGenerated between (ago(starttime)..ago(endtime)) + | where RecordType == szSharePointFileOperation + | where Operation in (szOperations) + | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) + | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) + | where isnotempty(UserAgent); + + // EnrichedMicrosoft365AuditLogs - Frequent User Agents + let FrequentUAEnriched = BaseeventsEnriched + | summarize FUACount = count() by UserAgent, RecordType, Operation + | where FUACount >= threshold + | distinct UserAgent; + + // EnrichedMicrosoft365AuditLogs - User Baseline + let UserBaseLineEnriched = BaseeventsEnriched + | summarize Count = count() by UserId, Operation, Site_Url + | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url; + + // EnrichedMicrosoft365AuditLogs - Recent User Activity + let RecentActivityEnriched = EnrichedMicrosoft365AuditLogs + | where TimeGenerated > ago(endtime) + | where RecordType == szSharePointFileOperation + | where Operation in (szOperations) | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) - | summarize count_distinct_ObjectId = dcount(ObjectId), dirlist = make_set(ObjectId, 10000) - by UserId, ClientIp, UserAgent, bin(TimeGenerated, 15m) - | where count_distinct_ObjectId >= threshold - | extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField", "_", tostring(hash(tostring(dirlist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), - AccountUPNSuffix = tostring(split(UserId, "@")[1]); + | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) + | where isnotempty(UserAgent) + | where UserAgent in (FrequentUAEnriched) + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ObjectIdCount = dcount(ObjectId), ObjectIdList = make_set(ObjectId), UserAgentSeenCount = count() + by RecordType, Operation, UserAgent, UserId, ClientIP, Site_Url; + + // Combine Baseline and Recent Activity, Calculate Deviation, and Deduplicate + let UserBehaviorAnalysisOffice = UserBaseLineOffice + | join kind=inner (RecentActivityOffice) on UserId, Operation, Site_Url + | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount; + + let UserBehaviorAnalysisEnriched = UserBaseLineEnriched + | join kind=inner (RecentActivityEnriched) on UserId, Operation, Site_Url + | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount; // Combine Office and Enriched Logs - let CombinedEvents = OfficeEvents - | union EnrichedEvents - | summarize arg_min(TimeGenerated, *) by UserId, ClientIp, UserAgent; + let CombinedUserBehaviorAnalysis = UserBehaviorAnalysisOffice + | union UserBehaviorAnalysisEnriched; - // Final Output - CombinedEvents - | project TimeGenerated, UserId, ClientIp, AccountName, AccountUPNSuffix, DirSample, count_distinct_SourceRelativeUrl, count_distinct_ObjectId, dirlist - | order by TimeGenerated desc + // Filter and Format Final Results + CombinedUserBehaviorAnalysis + | where Deviation > 0.25 + | extend UserIdName = tostring(split(UserId, '@')[0]), + UserIdUPNSuffix = tostring(split(UserId, '@')[1]) + | project-reorder StartTimeUtc, EndTimeUtc, UserAgent, UserAgentSeenCount, UserId, ClientIP, Site_Url + | order by UserAgentSeenCount desc, UserAgent asc, UserId asc, Site_Url asc entityMappings: - entityType: Account fieldMappings: - identifier: FullName columnName: UserId - identifier: Name - columnName: AccountName + columnName: UserIdName - identifier: UPNSuffix - columnName: AccountUPNSuffix + columnName: UserIdUPNSuffix - entityType: IP fieldMappings: - identifier: Address - columnName: ClientIp - - entityType: File + columnName: ClientIP + - entityType: URL fieldMappings: - - identifier: Name - columnName: DirSample -customDetails: - TransferCount: count_distinct_ObjectId - FilesList: dirlist -incidentConfiguration: - createIncident: true - groupingConfiguration: - enabled: true - reopenClosedIncident: false - lookbackDuration: 5h - matchingMethod: Selected - groupByEntities: - - Account - groupByAlertDetails: [] - groupByCustomDetails: [] -version: 2.0.7 + - identifier: Url + columnName: Site_Url +version: 2.2.6 kind: Scheduled From 8c991687753027bd78b30f4ff787f01fa836bc90 Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Wed, 2 Oct 2024 10:43:30 +0300 Subject: [PATCH 04/27] Fix IDs issues --- ... Mail_redirect_via_ExO_transport_rule.yaml | 98 ++++++------ ...- SharePoint_Downloads_byNewUserAgent.yaml | 149 +++++++++++------- ...file_transfer_folders_above_threshold.yaml | 149 +++++++----------- 3 files changed, 201 insertions(+), 195 deletions(-) diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml index c22cb23c12..8927e62141 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml @@ -1,59 +1,63 @@ -id: dc451755-8ab3-4059-b805-e454c45d1d44 -name: GSA Enriched Office 365 - Exchange AuditLog Disabled +id: edcfc2e0-3134-434c-8074-9101c530d419 +name: GSA Enriched Office 365 - Mail redirect via ExO transport rule description: | - Identifies when the Exchange audit logging has been disabled, which may indicate an adversary attempt to evade detection or bypass other defenses. + Identifies when Exchange Online transport rule configured to forward emails. + This could be an adversary mailbox configured to collect mail from multiple user accounts. severity: Medium -status: Available +status: Available requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs -queryFrequency: 1d -queryPeriod: 1d +queryFrequency: 1h +queryPeriod: 1h triggerOperator: gt triggerThreshold: 0 tactics: - - DefenseEvasion + - Collection + - Exfiltration relevantTechniques: - - T1562 + - T1114 + - T1020 query: | - // OfficeActivity Query - let OfficeEvents = OfficeActivity - | where OfficeWorkload =~ "Exchange" - | where UserType in~ ("Admin", "DcAdmin") - // Only admin or global-admin can disable audit logging - | where Operation =~ "Set-AdminAuditLogConfig" - | extend ParsedParameters = parse_json(Parameters) - | extend AdminAuditLogEnabledValue = tostring(ParsedParameters[3].Value) - | where AdminAuditLogEnabledValue =~ "False" - | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() - by Operation, UserType, UserId, ClientIP, ResultStatus, Parameters, AdminAuditLogEnabledValue - | extend AccountName = iff(UserId contains '@', tostring(split(UserId, "@")[0]), - iff(UserId contains '\\', tostring(split(UserId, "\\")[1]), UserId)) - | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, "@")[1]), '') - | extend AccountNTDomain = iff(UserId contains '\\', tostring(split(UserId, "\\")[0]), ''); - // EnrichedMicrosoft365AuditLogs Query - let EnrichedEvents = EnrichedMicrosoft365AuditLogs - | where Workload =~ "Exchange" - | where UserType in~ ("Admin", "DcAdmin") - | where Operation =~ "Set-AdminAuditLogConfig" - | extend ParsedParameters = parse_json(AdditionalProperties.Parameters) - | extend AdminAuditLogEnabledValue = tostring(ParsedParameters[3].Value) - | where AdminAuditLogEnabledValue =~ "False" - | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() - by Operation, UserType, UserId, ClientIP = SourceIp, ResultStatus, Parameters = tostring(AdditionalProperties.Parameters), AdminAuditLogEnabledValue - | extend AccountName = iff(UserId contains '@', tostring(split(UserId, "@")[0]), - iff(UserId contains '\\', tostring(split(UserId, "\\")[1]), UserId)) - | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, "@")[1]), '') - | extend AccountNTDomain = iff(UserId contains '\\', tostring(split(UserId, "\\")[0]), ''); - // Combine Office and Enriched Events and Deduplicate - let CombinedEvents = OfficeEvents - | union EnrichedEvents - | summarize arg_min(StartTimeUtc, *) by Operation, UserId, ClientIP; - // Project Final Output - CombinedEvents - | project StartTimeUtc, EndTimeUtc, Operation, UserType, UserId, ClientIP, ResultStatus, Parameters, AdminAuditLogEnabledValue, AccountName, AccountUPNSuffix, AccountNTDomain - | order by StartTimeUtc desc + let officeActivityQuery = OfficeActivity + | where OfficeWorkload == "Exchange" + | where Operation in~ ("New-TransportRule", "Set-TransportRule") + | mv-apply DynamicParameters = todynamic(Parameters) on (summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))) + | extend RuleName = case( + Operation =~ "Set-TransportRule", OfficeObjectId, + Operation =~ "New-TransportRule", ParsedParameters.Name, + "Unknown") + | mv-expand ExpandedParameters = todynamic(Parameters) + | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) + | extend RedirectTo = ExpandedParameters.Value + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] + | extend From = ParsedParameters.From + | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + let enrichedLogsQuery = EnrichedMicrosoft365AuditLogs + | where Workload == "Exchange" + | where Operation in~ ("New-TransportRule", "Set-TransportRule") + | mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on ( + summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) + ) + | extend RuleName = case( + Operation =~ "Set-TransportRule", ObjectId, // Assuming ObjectId maps to OfficeObjectId + Operation =~ "New-TransportRule", ParsedParameters.Name, + "Unknown" + ) + | mv-expand ExpandedParameters = todynamic(tostring(AdditionalProperties.Parameters)) + | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) + | extend RedirectTo = ExpandedParameters.Value + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] + | extend From = ParsedParameters.From + | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters = tostring(AdditionalProperties.Parameters) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine both queries + union isfuzzy=true officeActivityQuery, enrichedLogsQuery + | summarize arg_min(TimeGenerated, *) by RuleName, RedirectTo + | project TimeGenerated, RedirectTo, IPAddress, Port, UserId, From, Operation, RuleName, Parameters, AccountName, AccountUPNSuffix + | order by TimeGenerated desc; entityMappings: - entityType: Account fieldMappings: @@ -66,6 +70,6 @@ entityMappings: - entityType: IP fieldMappings: - identifier: Address - columnName: ClientIP -version: 2.0.8 + columnName: IPAddress +version: 2.1.4 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml index 28cacf36af..4508159a51 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml @@ -1,87 +1,124 @@ -id: 30375d00-68cc-4f95-b89a-68064d566358 -name: GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold +id: efd17c5f-5167-40f8-a1e9-0818940785d9 +name: GSA Enriched Office 365 - SharePointFileOperation via devices with previously unseen user agents description: | - Identifies Office365 Sharepoint File Transfers above a certain threshold in a 15-minute time period. - Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur. + Identifies anomalies if the number of documents uploaded or downloaded from device(s) associated with a previously unseen user agent exceeds a threshold (default is 5) and deviation (default is 25%). severity: Medium status: Available requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs -queryFrequency: 15m -queryPeriod: 15m +queryFrequency: 1d +queryPeriod: 14d triggerOperator: gt triggerThreshold: 0 tactics: - Exfiltration relevantTechniques: - - T1020 + - T1030 query: | - let threshold = 5000; + let threshold = 5; + let szSharePointFileOperation = "SharePointFileOperation"; + let szOperations = dynamic(["FileDownloaded", "FileUploaded"]); + let starttime = 14d; + let endtime = 1d; - // EnrichedMicrosoft365AuditLogs Query - let EnrichedEvents = EnrichedMicrosoft365AuditLogs - | where Workload has_any("SharePoint", "OneDrive") - | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | summarize count_distinct_ObjectId = dcount(ObjectId), fileslist = make_set(ObjectId, 10000) - by UserId, ClientIP, bin(TimeGenerated, 15m) - | where count_distinct_ObjectId >= threshold - | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), - AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // OfficeActivity - Base Events + let BaseeventsOffice = OfficeActivity + | where TimeGenerated between (ago(starttime)..ago(endtime)) + | where RecordType =~ szSharePointFileOperation + | where Operation in~ (szOperations) + | where isnotempty(UserAgent); - // OfficeActivity Query - let OfficeEvents = OfficeActivity - | where EventSource == "SharePoint" - | where OfficeWorkload has_any("SharePoint", "OneDrive") - | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | summarize count_distinct_OfficeObjectId = dcount(OfficeObjectId), fileslist = make_set(OfficeObjectId, 10000) - by UserId, ClientIP, bin(TimeGenerated, 15m) - | where count_distinct_OfficeObjectId >= threshold - | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), - AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // OfficeActivity - Frequent User Agents + let FrequentUAOffice = BaseeventsOffice + | summarize FUACount = count() by UserAgent, RecordType, Operation + | where FUACount >= threshold + | distinct UserAgent; + + // OfficeActivity - User Baseline + let UserBaseLineOffice = BaseeventsOffice + | summarize Count = count() by UserId, Operation, Site_Url + | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url; + + // OfficeActivity - Recent User Activity + let RecentActivityOffice = OfficeActivity + | where TimeGenerated > ago(endtime) + | where RecordType =~ szSharePointFileOperation + | where Operation in~ (szOperations) + | where isnotempty(UserAgent) + | where UserAgent in~ (FrequentUAOffice) + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OfficeObjectIdCount = dcount(OfficeObjectId), OfficeObjectIdList = make_set(OfficeObjectId), UserAgentSeenCount = count() + by RecordType, Operation, UserAgent, UserType, UserId, ClientIP, OfficeWorkload, Site_Url; + + // EnrichedMicrosoft365AuditLogs - Base Events + let BaseeventsEnriched = EnrichedMicrosoft365AuditLogs + | where TimeGenerated between (ago(starttime)..ago(endtime)) + | where RecordType == szSharePointFileOperation + | where Operation in (szOperations) + | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) + | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) + | where isnotempty(UserAgent); + + // EnrichedMicrosoft365AuditLogs - Frequent User Agents + let FrequentUAEnriched = BaseeventsEnriched + | summarize FUACount = count() by UserAgent, RecordType, Operation + | where FUACount >= threshold + | distinct UserAgent; + + // EnrichedMicrosoft365AuditLogs - User Baseline + let UserBaseLineEnriched = BaseeventsEnriched + | summarize Count = count() by UserId, Operation, Site_Url + | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url; + + // EnrichedMicrosoft365AuditLogs - Recent User Activity + let RecentActivityEnriched = EnrichedMicrosoft365AuditLogs + | where TimeGenerated > ago(endtime) + | where RecordType == szSharePointFileOperation + | where Operation in (szOperations) + | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) + | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) + | where isnotempty(UserAgent) + | where UserAgent in (FrequentUAEnriched) + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ObjectIdCount = dcount(ObjectId), ObjectIdList = make_set(ObjectId), UserAgentSeenCount = count() + by RecordType, Operation, UserAgent, UserId, ClientIP, Site_Url; + + // Combine Baseline and Recent Activity, Calculate Deviation, and Deduplicate + let UserBehaviorAnalysisOffice = UserBaseLineOffice + | join kind=inner (RecentActivityOffice) on UserId, Operation, Site_Url + | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount; + + let UserBehaviorAnalysisEnriched = UserBaseLineEnriched + | join kind=inner (RecentActivityEnriched) on UserId, Operation, Site_Url + | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount; // Combine Office and Enriched Logs - let CombinedEvents = OfficeEvents - | union EnrichedEvents - | summarize arg_min(TimeGenerated, *) by UserId, ClientIP; + let CombinedUserBehaviorAnalysis = UserBehaviorAnalysisOffice + | union UserBehaviorAnalysisEnriched; - // Final Output - CombinedEvents - | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist - | order by TimeGenerated desc + // Filter and Format Final Results + CombinedUserBehaviorAnalysis + | where Deviation > 0.25 + | extend UserIdName = tostring(split(UserId, '@')[0]), + UserIdUPNSuffix = tostring(split(UserId, '@')[1]) + | project-reorder StartTimeUtc, EndTimeUtc, UserAgent, UserAgentSeenCount, UserId, ClientIP, Site_Url + | order by UserAgentSeenCount desc, UserAgent asc, UserId asc, Site_Url asc entityMappings: - entityType: Account fieldMappings: - identifier: FullName columnName: UserId - identifier: Name - columnName: AccountName + columnName: UserIdName - identifier: UPNSuffix - columnName: AccountUPNSuffix + columnName: UserIdUPNSuffix - entityType: IP fieldMappings: - identifier: Address columnName: ClientIP - - entityType: File + - entityType: URL fieldMappings: - - identifier: Name - columnName: FileSample -customDetails: - TransferCount: count_distinct_ObjectId - FilesList: fileslist -incidentConfiguration: - createIncident: true - groupingConfiguration: - enabled: true - reopenClosedIncident: false - lookbackDuration: 5h - matchingMethod: Selected - groupByEntities: - - Account - groupByAlertDetails: [] - groupByCustomDetails: [] -version: 2.0.6 + - identifier: Url + columnName: Site_Url +version: 2.2.6 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml index 4508159a51..08958fec1f 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml @@ -1,124 +1,89 @@ -id: efd17c5f-5167-40f8-a1e9-0818940785d9 -name: GSA Enriched Office 365 - SharePointFileOperation via devices with previously unseen user agents +id: abd6976d-8f71-4851-98c4-4d086201319c +name: GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold description: | - Identifies anomalies if the number of documents uploaded or downloaded from device(s) associated with a previously unseen user agent exceeds a threshold (default is 5) and deviation (default is 25%). + Identifies Office365 Sharepoint File Transfers with a distinct folder count above a certain threshold in a 15-minute time period. + Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur. severity: Medium status: Available requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs -queryFrequency: 1d -queryPeriod: 14d +queryFrequency: 15m +queryPeriod: 15m triggerOperator: gt triggerThreshold: 0 tactics: - Exfiltration relevantTechniques: - - T1030 + - T1020 query: | - let threshold = 5; - let szSharePointFileOperation = "SharePointFileOperation"; - let szOperations = dynamic(["FileDownloaded", "FileUploaded"]); - let starttime = 14d; - let endtime = 1d; + let threshold = 500; - // OfficeActivity - Base Events - let BaseeventsOffice = OfficeActivity - | where TimeGenerated between (ago(starttime)..ago(endtime)) - | where RecordType =~ szSharePointFileOperation - | where Operation in~ (szOperations) - | where isnotempty(UserAgent); + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where EventSource == "SharePoint" + | where OfficeWorkload has_any("SharePoint", "OneDrive") + | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | summarize count_distinct_SourceRelativeUrl = dcount(SourceRelativeUrl), dirlist = make_set(SourceRelativeUrl, 10000) + by UserId, ClientIP, UserAgent, bin(TimeGenerated, 15m) + | where count_distinct_SourceRelativeUrl >= threshold + | extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField", "_", tostring(hash(tostring(dirlist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), + AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // OfficeActivity - Frequent User Agents - let FrequentUAOffice = BaseeventsOffice - | summarize FUACount = count() by UserAgent, RecordType, Operation - | where FUACount >= threshold - | distinct UserAgent; - - // OfficeActivity - User Baseline - let UserBaseLineOffice = BaseeventsOffice - | summarize Count = count() by UserId, Operation, Site_Url - | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url; - - // OfficeActivity - Recent User Activity - let RecentActivityOffice = OfficeActivity - | where TimeGenerated > ago(endtime) - | where RecordType =~ szSharePointFileOperation - | where Operation in~ (szOperations) - | where isnotempty(UserAgent) - | where UserAgent in~ (FrequentUAOffice) - | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OfficeObjectIdCount = dcount(OfficeObjectId), OfficeObjectIdList = make_set(OfficeObjectId), UserAgentSeenCount = count() - by RecordType, Operation, UserAgent, UserType, UserId, ClientIP, OfficeWorkload, Site_Url; - - // EnrichedMicrosoft365AuditLogs - Base Events - let BaseeventsEnriched = EnrichedMicrosoft365AuditLogs - | where TimeGenerated between (ago(starttime)..ago(endtime)) - | where RecordType == szSharePointFileOperation - | where Operation in (szOperations) - | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) - | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) - | where isnotempty(UserAgent); - - // EnrichedMicrosoft365AuditLogs - Frequent User Agents - let FrequentUAEnriched = BaseeventsEnriched - | summarize FUACount = count() by UserAgent, RecordType, Operation - | where FUACount >= threshold - | distinct UserAgent; - - // EnrichedMicrosoft365AuditLogs - User Baseline - let UserBaseLineEnriched = BaseeventsEnriched - | summarize Count = count() by UserId, Operation, Site_Url - | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url; - - // EnrichedMicrosoft365AuditLogs - Recent User Activity - let RecentActivityEnriched = EnrichedMicrosoft365AuditLogs - | where TimeGenerated > ago(endtime) - | where RecordType == szSharePointFileOperation - | where Operation in (szOperations) + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload has_any("SharePoint", "OneDrive") + | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | extend EventSource = tostring(parse_json(tostring(AdditionalProperties)).EventSource) | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) - | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) - | where isnotempty(UserAgent) - | where UserAgent in (FrequentUAEnriched) - | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ObjectIdCount = dcount(ObjectId), ObjectIdList = make_set(ObjectId), UserAgentSeenCount = count() - by RecordType, Operation, UserAgent, UserId, ClientIP, Site_Url; - - // Combine Baseline and Recent Activity, Calculate Deviation, and Deduplicate - let UserBehaviorAnalysisOffice = UserBaseLineOffice - | join kind=inner (RecentActivityOffice) on UserId, Operation, Site_Url - | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount; - - let UserBehaviorAnalysisEnriched = UserBaseLineEnriched - | join kind=inner (RecentActivityEnriched) on UserId, Operation, Site_Url - | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount; + | summarize count_distinct_ObjectId = dcount(ObjectId), dirlist = make_set(ObjectId, 10000) + by UserId, ClientIP, UserAgent, bin(TimeGenerated, 15m) + | where count_distinct_ObjectId >= threshold + | extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField", "_", tostring(hash(tostring(dirlist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), + AccountUPNSuffix = tostring(split(UserId, "@")[1]); // Combine Office and Enriched Logs - let CombinedUserBehaviorAnalysis = UserBehaviorAnalysisOffice - | union UserBehaviorAnalysisEnriched; + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(TimeGenerated, *) by UserId, ClientIP, UserAgent; - // Filter and Format Final Results - CombinedUserBehaviorAnalysis - | where Deviation > 0.25 - | extend UserIdName = tostring(split(UserId, '@')[0]), - UserIdUPNSuffix = tostring(split(UserId, '@')[1]) - | project-reorder StartTimeUtc, EndTimeUtc, UserAgent, UserAgentSeenCount, UserId, ClientIP, Site_Url - | order by UserAgentSeenCount desc, UserAgent asc, UserId asc, Site_Url asc + // Final Output + CombinedEvents + | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, DirSample, count_distinct_SourceRelativeUrl, count_distinct_ObjectId, dirlist + | order by TimeGenerated desc entityMappings: - entityType: Account fieldMappings: - identifier: FullName columnName: UserId - identifier: Name - columnName: UserIdName + columnName: AccountName - identifier: UPNSuffix - columnName: UserIdUPNSuffix + columnName: AccountUPNSuffix - entityType: IP fieldMappings: - identifier: Address columnName: ClientIP - - entityType: URL + - entityType: File fieldMappings: - - identifier: Url - columnName: Site_Url -version: 2.2.6 + - identifier: Name + columnName: DirSample +customDetails: + TransferCount: count_distinct_ObjectId + FilesList: dirlist +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + groupByAlertDetails: [] + groupByCustomDetails: [] +version: 2.0.7 kind: Scheduled From e8fb60005966ddee56956cccf4ee98c5759265fd Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Wed, 2 Oct 2024 11:08:40 +0300 Subject: [PATCH 05/27] KQL Issues --- ... Mail_redirect_via_ExO_transport_rule.yaml | 25 ++++++----- ...repoint_file_transfer_above_threshold.yaml | 16 +++---- ...file_transfer_folders_above_threshold.yaml | 44 +++++++++---------- 3 files changed, 44 insertions(+), 41 deletions(-) diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml index 8927e62141..d7f6ad2cfd 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml @@ -1,10 +1,10 @@ id: edcfc2e0-3134-434c-8074-9101c530d419 -name: GSA Enriched Office 365 - Mail redirect via ExO transport rule +name: GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule description: | - Identifies when Exchange Online transport rule configured to forward emails. - This could be an adversary mailbox configured to collect mail from multiple user accounts. + Identifies when an Exchange Online transport rule is configured to forward emails. + This could indicate an adversary mailbox configured to collect mail from multiple user accounts. severity: Medium -status: Available +status: Available requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: @@ -23,18 +23,22 @@ query: | let officeActivityQuery = OfficeActivity | where OfficeWorkload == "Exchange" | where Operation in~ ("New-TransportRule", "Set-TransportRule") - | mv-apply DynamicParameters = todynamic(Parameters) on (summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))) + | mv-apply DynamicParameters = todynamic(Parameters) on ( + summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) + ) | extend RuleName = case( Operation =~ "Set-TransportRule", OfficeObjectId, Operation =~ "New-TransportRule", ParsedParameters.Name, - "Unknown") + "Unknown" + ) | mv-expand ExpandedParameters = todynamic(Parameters) | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) | extend RedirectTo = ExpandedParameters.Value - | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIp)[0] | extend From = ParsedParameters.From | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + | extend AccountName = tostring(split(UserId, "@")[0]), + AccountUPNSuffix = tostring(split(UserId, "@")[1]); let enrichedLogsQuery = EnrichedMicrosoft365AuditLogs | where Workload == "Exchange" | where Operation in~ ("New-TransportRule", "Set-TransportRule") @@ -49,10 +53,11 @@ query: | | mv-expand ExpandedParameters = todynamic(tostring(AdditionalProperties.Parameters)) | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) | extend RedirectTo = ExpandedParameters.Value - | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIp)[0] | extend From = ParsedParameters.From | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters = tostring(AdditionalProperties.Parameters) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + | extend AccountName = tostring(split(UserId, "@")[0]), + AccountUPNSuffix = tostring(split(UserId, "@")[1]); // Combine both queries union isfuzzy=true officeActivityQuery, enrichedLogsQuery | summarize arg_min(TimeGenerated, *) by RuleName, RedirectTo diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml index 2a3ac82eaa..43db365da9 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml @@ -24,8 +24,8 @@ query: | let EnrichedEvents = EnrichedMicrosoft365AuditLogs | where Workload has_any("SharePoint", "OneDrive") | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | summarize count_distinct_ObjectId=dcount(ObjectId), fileslist=make_set(ObjectId, 10000) - by UserId, ClientIp, bin(TimeGenerated, 15m) + | summarize count_distinct_ObjectId = dcount(ObjectId), fileslist = make_set(ObjectId, 10000) + by UserId, ClientIP, bin(TimeGenerated, 15m) | where count_distinct_ObjectId >= threshold | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) | extend AccountName = tostring(split(UserId, "@")[0]), @@ -36,8 +36,8 @@ query: | | where EventSource == "SharePoint" | where OfficeWorkload has_any("SharePoint", "OneDrive") | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | summarize count_distinct_OfficeObjectId=dcount(OfficeObjectId), fileslist=make_set(OfficeObjectId, 10000) - by UserId, ClientIp, bin(TimeGenerated, 15m) + | summarize count_distinct_OfficeObjectId = dcount(OfficeObjectId), fileslist = make_set(OfficeObjectId, 10000) + by UserId, ClientIP, bin(TimeGenerated, 15m) | where count_distinct_OfficeObjectId >= threshold | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) | extend AccountName = tostring(split(UserId, "@")[0]), @@ -46,11 +46,11 @@ query: | // Combine Office and Enriched Logs let CombinedEvents = OfficeEvents | union EnrichedEvents - | summarize arg_min(TimeGenerated, *) by UserId, ClientIp; + | summarize arg_min(TimeGenerated, *) by UserId, ClientIP; // Final Output CombinedEvents - | project TimeGenerated, UserId, ClientIp, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist + | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist | order by TimeGenerated desc entityMappings: - entityType: Account @@ -64,7 +64,7 @@ entityMappings: - entityType: IP fieldMappings: - identifier: Address - columnName: ClientIp + columnName: ClientIP - entityType: File fieldMappings: - identifier: Name @@ -83,5 +83,5 @@ incidentConfiguration: - Account groupByAlertDetails: [] groupByCustomDetails: [] -version: 2.0.6 +version: 2.0.7 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml index 08958fec1f..bae75e117f 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml @@ -18,41 +18,39 @@ tactics: relevantTechniques: - T1020 query: | - let threshold = 500; + let threshold = 5000; - // OfficeActivity Query - let OfficeEvents = OfficeActivity - | where EventSource == "SharePoint" - | where OfficeWorkload has_any("SharePoint", "OneDrive") + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload has_any("SharePoint", "OneDrive") | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | summarize count_distinct_SourceRelativeUrl = dcount(SourceRelativeUrl), dirlist = make_set(SourceRelativeUrl, 10000) - by UserId, ClientIP, UserAgent, bin(TimeGenerated, 15m) - | where count_distinct_SourceRelativeUrl >= threshold - | extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField", "_", tostring(hash(tostring(dirlist))))) + | summarize count_distinct_ObjectId = dcount(ObjectId), fileslist = make_set(ObjectId, 10000) + by UserId, ClientIp = SourceIp, bin(TimeGenerated, 15m) + | where count_distinct_ObjectId >= threshold + | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // EnrichedMicrosoft365AuditLogs Query - let EnrichedEvents = EnrichedMicrosoft365AuditLogs - | where Workload has_any("SharePoint", "OneDrive") + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where EventSource == "SharePoint" + | where OfficeWorkload has_any("SharePoint", "OneDrive") | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | extend EventSource = tostring(parse_json(tostring(AdditionalProperties)).EventSource) - | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) - | summarize count_distinct_ObjectId = dcount(ObjectId), dirlist = make_set(ObjectId, 10000) - by UserId, ClientIP, UserAgent, bin(TimeGenerated, 15m) - | where count_distinct_ObjectId >= threshold - | extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField", "_", tostring(hash(tostring(dirlist))))) + | summarize count_distinct_OfficeObjectId = dcount(OfficeObjectId), fileslist = make_set(OfficeObjectId, 10000) + by UserId, ClientIp, bin(TimeGenerated, 15m) + | where count_distinct_OfficeObjectId >= threshold + | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); // Combine Office and Enriched Logs let CombinedEvents = OfficeEvents | union EnrichedEvents - | summarize arg_min(TimeGenerated, *) by UserId, ClientIP, UserAgent; + | summarize arg_min(TimeGenerated, *) by UserId, ClientIp, UserAgent; // Final Output CombinedEvents - | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, DirSample, count_distinct_SourceRelativeUrl, count_distinct_ObjectId, dirlist + | project TimeGenerated, UserId, ClientIp, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist | order by TimeGenerated desc entityMappings: - entityType: Account @@ -66,14 +64,14 @@ entityMappings: - entityType: IP fieldMappings: - identifier: Address - columnName: ClientIP + columnName: ClientIp - entityType: File fieldMappings: - identifier: Name - columnName: DirSample + columnName: FileSample customDetails: TransferCount: count_distinct_ObjectId - FilesList: dirlist + FilesList: fileslist incidentConfiguration: createIncident: true groupingConfiguration: From f2f7140f37ed1bd5026e053792372957a0de690c Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Wed, 2 Oct 2024 11:36:29 +0300 Subject: [PATCH 06/27] ClientIp issues --- ... Mail_redirect_via_ExO_transport_rule.yaml | 9 +- ...- SharePoint_Downloads_byNewUserAgent.yaml | 149 +++++++----------- ...repoint_file_transfer_above_threshold.yaml | 2 +- ...file_transfer_folders_above_threshold.yaml | 11 +- 4 files changed, 69 insertions(+), 102 deletions(-) diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml index d7f6ad2cfd..bdc22aa86b 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml @@ -34,11 +34,12 @@ query: | | mv-expand ExpandedParameters = todynamic(Parameters) | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) | extend RedirectTo = ExpandedParameters.Value - | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIp)[0] + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] | extend From = ParsedParameters.From | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + let enrichedLogsQuery = EnrichedMicrosoft365AuditLogs | where Workload == "Exchange" | where Operation in~ ("New-TransportRule", "Set-TransportRule") @@ -53,11 +54,13 @@ query: | | mv-expand ExpandedParameters = todynamic(tostring(AdditionalProperties.Parameters)) | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) | extend RedirectTo = ExpandedParameters.Value - | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIp)[0] + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] | extend From = ParsedParameters.From - | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters = tostring(AdditionalProperties.Parameters) + | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) + | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters = tostring(AdditionalProperties.Parameters), UserAgent | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine both queries union isfuzzy=true officeActivityQuery, enrichedLogsQuery | summarize arg_min(TimeGenerated, *) by RuleName, RedirectTo diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml index 4508159a51..71c4021df2 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml @@ -1,124 +1,87 @@ -id: efd17c5f-5167-40f8-a1e9-0818940785d9 -name: GSA Enriched Office 365 - SharePointFileOperation via devices with previously unseen user agents +id: 30375d00-68cc-4f95-b89a-68064d566358 +name: GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold description: | - Identifies anomalies if the number of documents uploaded or downloaded from device(s) associated with a previously unseen user agent exceeds a threshold (default is 5) and deviation (default is 25%). + Identifies Office365 Sharepoint File Transfers above a certain threshold in a 15-minute time period. + Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur. severity: Medium status: Available requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs -queryFrequency: 1d -queryPeriod: 14d +queryFrequency: 15m +queryPeriod: 15m triggerOperator: gt triggerThreshold: 0 tactics: - Exfiltration relevantTechniques: - - T1030 + - T1020 query: | - let threshold = 5; - let szSharePointFileOperation = "SharePointFileOperation"; - let szOperations = dynamic(["FileDownloaded", "FileUploaded"]); - let starttime = 14d; - let endtime = 1d; + let threshold = 5000; - // OfficeActivity - Base Events - let BaseeventsOffice = OfficeActivity - | where TimeGenerated between (ago(starttime)..ago(endtime)) - | where RecordType =~ szSharePointFileOperation - | where Operation in~ (szOperations) - | where isnotempty(UserAgent); + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload has_any("SharePoint", "OneDrive") + | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | summarize count_distinct_ObjectId = dcount(ObjectId), fileslist = make_set(ObjectId, 10000) + by UserId, ClientIP = ClientIp, bin(TimeGenerated, 15m) + | where count_distinct_ObjectId >= threshold + | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), + AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // OfficeActivity - Frequent User Agents - let FrequentUAOffice = BaseeventsOffice - | summarize FUACount = count() by UserAgent, RecordType, Operation - | where FUACount >= threshold - | distinct UserAgent; - - // OfficeActivity - User Baseline - let UserBaseLineOffice = BaseeventsOffice - | summarize Count = count() by UserId, Operation, Site_Url - | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url; - - // OfficeActivity - Recent User Activity - let RecentActivityOffice = OfficeActivity - | where TimeGenerated > ago(endtime) - | where RecordType =~ szSharePointFileOperation - | where Operation in~ (szOperations) - | where isnotempty(UserAgent) - | where UserAgent in~ (FrequentUAOffice) - | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OfficeObjectIdCount = dcount(OfficeObjectId), OfficeObjectIdList = make_set(OfficeObjectId), UserAgentSeenCount = count() - by RecordType, Operation, UserAgent, UserType, UserId, ClientIP, OfficeWorkload, Site_Url; - - // EnrichedMicrosoft365AuditLogs - Base Events - let BaseeventsEnriched = EnrichedMicrosoft365AuditLogs - | where TimeGenerated between (ago(starttime)..ago(endtime)) - | where RecordType == szSharePointFileOperation - | where Operation in (szOperations) - | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) - | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) - | where isnotempty(UserAgent); - - // EnrichedMicrosoft365AuditLogs - Frequent User Agents - let FrequentUAEnriched = BaseeventsEnriched - | summarize FUACount = count() by UserAgent, RecordType, Operation - | where FUACount >= threshold - | distinct UserAgent; - - // EnrichedMicrosoft365AuditLogs - User Baseline - let UserBaseLineEnriched = BaseeventsEnriched - | summarize Count = count() by UserId, Operation, Site_Url - | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url; - - // EnrichedMicrosoft365AuditLogs - Recent User Activity - let RecentActivityEnriched = EnrichedMicrosoft365AuditLogs - | where TimeGenerated > ago(endtime) - | where RecordType == szSharePointFileOperation - | where Operation in (szOperations) - | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) - | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) - | where isnotempty(UserAgent) - | where UserAgent in (FrequentUAEnriched) - | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ObjectIdCount = dcount(ObjectId), ObjectIdList = make_set(ObjectId), UserAgentSeenCount = count() - by RecordType, Operation, UserAgent, UserId, ClientIP, Site_Url; - - // Combine Baseline and Recent Activity, Calculate Deviation, and Deduplicate - let UserBehaviorAnalysisOffice = UserBaseLineOffice - | join kind=inner (RecentActivityOffice) on UserId, Operation, Site_Url - | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount; - - let UserBehaviorAnalysisEnriched = UserBaseLineEnriched - | join kind=inner (RecentActivityEnriched) on UserId, Operation, Site_Url - | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount; + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where EventSource == "SharePoint" + | where OfficeWorkload has_any("SharePoint", "OneDrive") + | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | summarize count_distinct_OfficeObjectId = dcount(OfficeObjectId), fileslist = make_set(OfficeObjectId, 10000) + by UserId, ClientIP, bin(TimeGenerated, 15m) + | where count_distinct_OfficeObjectId >= threshold + | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), + AccountUPNSuffix = tostring(split(UserId, "@")[1]); // Combine Office and Enriched Logs - let CombinedUserBehaviorAnalysis = UserBehaviorAnalysisOffice - | union UserBehaviorAnalysisEnriched; + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(TimeGenerated, *) by UserId, ClientIP, UserAgent; - // Filter and Format Final Results - CombinedUserBehaviorAnalysis - | where Deviation > 0.25 - | extend UserIdName = tostring(split(UserId, '@')[0]), - UserIdUPNSuffix = tostring(split(UserId, '@')[1]) - | project-reorder StartTimeUtc, EndTimeUtc, UserAgent, UserAgentSeenCount, UserId, ClientIP, Site_Url - | order by UserAgentSeenCount desc, UserAgent asc, UserId asc, Site_Url asc + // Final Output + CombinedEvents + | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist + | order by TimeGenerated desc entityMappings: - entityType: Account fieldMappings: - identifier: FullName columnName: UserId - identifier: Name - columnName: UserIdName + columnName: AccountName - identifier: UPNSuffix - columnName: UserIdUPNSuffix + columnName: AccountUPNSuffix - entityType: IP fieldMappings: - identifier: Address columnName: ClientIP - - entityType: URL + - entityType: File fieldMappings: - - identifier: Url - columnName: Site_Url -version: 2.2.6 + - identifier: Name + columnName: FileSample +customDetails: + TransferCount: count_distinct_ObjectId + FilesList: fileslist +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: true + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: Selected + groupByEntities: + - Account + groupByAlertDetails: [] + groupByCustomDetails: [] +version: 2.0.7 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml index 43db365da9..1fc244436a 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml @@ -25,7 +25,7 @@ query: | | where Workload has_any("SharePoint", "OneDrive") | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") | summarize count_distinct_ObjectId = dcount(ObjectId), fileslist = make_set(ObjectId, 10000) - by UserId, ClientIP, bin(TimeGenerated, 15m) + by UserId, ClientIP = ClientIp, bin(TimeGenerated, 15m) | where count_distinct_ObjectId >= threshold | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) | extend AccountName = tostring(split(UserId, "@")[0]), diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml index bae75e117f..fe06f44962 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml @@ -25,8 +25,9 @@ query: | | where Workload has_any("SharePoint", "OneDrive") | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") | summarize count_distinct_ObjectId = dcount(ObjectId), fileslist = make_set(ObjectId, 10000) - by UserId, ClientIp = SourceIp, bin(TimeGenerated, 15m) + by UserId, ClientIP = ClientIp, bin(TimeGenerated, 15m) | where count_distinct_ObjectId >= threshold + | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); @@ -37,7 +38,7 @@ query: | | where OfficeWorkload has_any("SharePoint", "OneDrive") | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") | summarize count_distinct_OfficeObjectId = dcount(OfficeObjectId), fileslist = make_set(OfficeObjectId, 10000) - by UserId, ClientIp, bin(TimeGenerated, 15m) + by UserId, ClientIP, UserAgent, bin(TimeGenerated, 15m) | where count_distinct_OfficeObjectId >= threshold | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) | extend AccountName = tostring(split(UserId, "@")[0]), @@ -46,11 +47,11 @@ query: | // Combine Office and Enriched Logs let CombinedEvents = OfficeEvents | union EnrichedEvents - | summarize arg_min(TimeGenerated, *) by UserId, ClientIp, UserAgent; + | summarize arg_min(TimeGenerated, *) by UserId, ClientIP, UserAgent; // Final Output CombinedEvents - | project TimeGenerated, UserId, ClientIp, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist + | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist | order by TimeGenerated desc entityMappings: - entityType: Account @@ -64,7 +65,7 @@ entityMappings: - entityType: IP fieldMappings: - identifier: Address - columnName: ClientIp + columnName: ClientIP - entityType: File fieldMappings: - identifier: Name From 5fb79387794441ee30926d7e8742642e3edb7ee5 Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Wed, 2 Oct 2024 12:00:59 +0300 Subject: [PATCH 07/27] KQL issues --- ... Mail_redirect_via_ExO_transport_rule.yaml | 2 +- ...- SharePoint_Downloads_byNewUserAgent.yaml | 2 +- ...file_transfer_folders_above_threshold.yaml | 49 ++++++++----------- 3 files changed, 22 insertions(+), 31 deletions(-) diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml index bdc22aa86b..eabfa1226a 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml @@ -54,7 +54,7 @@ query: | | mv-expand ExpandedParameters = todynamic(tostring(AdditionalProperties.Parameters)) | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) | extend RedirectTo = ExpandedParameters.Value - | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIp)[0] | extend From = ParsedParameters.From | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters = tostring(AdditionalProperties.Parameters), UserAgent diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml index 71c4021df2..1fc244436a 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml @@ -46,7 +46,7 @@ query: | // Combine Office and Enriched Logs let CombinedEvents = OfficeEvents | union EnrichedEvents - | summarize arg_min(TimeGenerated, *) by UserId, ClientIP, UserAgent; + | summarize arg_min(TimeGenerated, *) by UserId, ClientIP; // Final Output CombinedEvents diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml index fe06f44962..eb68d9e29b 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml @@ -1,8 +1,7 @@ id: abd6976d-8f71-4851-98c4-4d086201319c name: GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold description: | - Identifies Office365 Sharepoint File Transfers with a distinct folder count above a certain threshold in a 15-minute time period. - Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur. + Identifies Office365 Sharepoint File Transfers with a distinct folder count above a certain threshold in a 15-minute time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur. severity: Medium status: Available requiredDataConnectors: @@ -19,40 +18,32 @@ relevantTechniques: - T1020 query: | let threshold = 5000; - // EnrichedMicrosoft365AuditLogs Query let EnrichedEvents = EnrichedMicrosoft365AuditLogs - | where Workload has_any("SharePoint", "OneDrive") - | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | summarize count_distinct_ObjectId = dcount(ObjectId), fileslist = make_set(ObjectId, 10000) - by UserId, ClientIP = ClientIp, bin(TimeGenerated, 15m) - | where count_distinct_ObjectId >= threshold - | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) - | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), - AccountUPNSuffix = tostring(split(UserId, "@")[1]); - + | where Workload has_any("SharePoint", "OneDrive") + | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | summarize count_distinct_ObjectId = dcount(ObjectId), fileslist = make_set(ObjectId, 10000) by UserId, ClientIP = ClientIp, bin(TimeGenerated, 15m) + | where count_distinct_ObjectId >= threshold + | extend UserAgent = tostring(AdditionalProperties.UserAgent) + | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); // OfficeActivity Query let OfficeEvents = OfficeActivity - | where EventSource == "SharePoint" - | where OfficeWorkload has_any("SharePoint", "OneDrive") - | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | summarize count_distinct_OfficeObjectId = dcount(OfficeObjectId), fileslist = make_set(OfficeObjectId, 10000) - by UserId, ClientIP, UserAgent, bin(TimeGenerated, 15m) - | where count_distinct_OfficeObjectId >= threshold - | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), - AccountUPNSuffix = tostring(split(UserId, "@")[1]); - + | where EventSource == "SharePoint" + | where OfficeWorkload has_any("SharePoint", "OneDrive") + | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | summarize count_distinct_OfficeObjectId = dcount(OfficeObjectId), fileslist = make_set(OfficeObjectId, 10000) by UserId, ClientIP, UserAgent, bin(TimeGenerated, 15m) + | where count_distinct_OfficeObjectId >= threshold + | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); // Combine Office and Enriched Logs let CombinedEvents = OfficeEvents - | union EnrichedEvents - | summarize arg_min(TimeGenerated, *) by UserId, ClientIP, UserAgent; - + | union EnrichedEvents + | summarize arg_min(TimeGenerated, *) by UserId, ClientIP, UserAgent; // Final Output CombinedEvents - | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist - | order by TimeGenerated desc + | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist + | order by TimeGenerated desc entityMappings: - entityType: Account fieldMappings: @@ -85,4 +76,4 @@ incidentConfiguration: groupByAlertDetails: [] groupByCustomDetails: [] version: 2.0.7 -kind: Scheduled +kind: Scheduled \ No newline at end of file From 8c6bc0d3e3e63c239edbc16d968f591a09939e9c Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Wed, 2 Oct 2024 12:28:14 +0300 Subject: [PATCH 08/27] Fixing KQLs --- ...- SharePoint_Downloads_byNewUserAgent.yaml | 149 +++++++++++------- ...file_transfer_folders_above_threshold.yaml | 60 +++---- 2 files changed, 126 insertions(+), 83 deletions(-) diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml index 1fc244436a..4508159a51 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml @@ -1,87 +1,124 @@ -id: 30375d00-68cc-4f95-b89a-68064d566358 -name: GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold +id: efd17c5f-5167-40f8-a1e9-0818940785d9 +name: GSA Enriched Office 365 - SharePointFileOperation via devices with previously unseen user agents description: | - Identifies Office365 Sharepoint File Transfers above a certain threshold in a 15-minute time period. - Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur. + Identifies anomalies if the number of documents uploaded or downloaded from device(s) associated with a previously unseen user agent exceeds a threshold (default is 5) and deviation (default is 25%). severity: Medium status: Available requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs -queryFrequency: 15m -queryPeriod: 15m +queryFrequency: 1d +queryPeriod: 14d triggerOperator: gt triggerThreshold: 0 tactics: - Exfiltration relevantTechniques: - - T1020 + - T1030 query: | - let threshold = 5000; + let threshold = 5; + let szSharePointFileOperation = "SharePointFileOperation"; + let szOperations = dynamic(["FileDownloaded", "FileUploaded"]); + let starttime = 14d; + let endtime = 1d; - // EnrichedMicrosoft365AuditLogs Query - let EnrichedEvents = EnrichedMicrosoft365AuditLogs - | where Workload has_any("SharePoint", "OneDrive") - | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | summarize count_distinct_ObjectId = dcount(ObjectId), fileslist = make_set(ObjectId, 10000) - by UserId, ClientIP = ClientIp, bin(TimeGenerated, 15m) - | where count_distinct_ObjectId >= threshold - | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), - AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // OfficeActivity - Base Events + let BaseeventsOffice = OfficeActivity + | where TimeGenerated between (ago(starttime)..ago(endtime)) + | where RecordType =~ szSharePointFileOperation + | where Operation in~ (szOperations) + | where isnotempty(UserAgent); - // OfficeActivity Query - let OfficeEvents = OfficeActivity - | where EventSource == "SharePoint" - | where OfficeWorkload has_any("SharePoint", "OneDrive") - | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | summarize count_distinct_OfficeObjectId = dcount(OfficeObjectId), fileslist = make_set(OfficeObjectId, 10000) - by UserId, ClientIP, bin(TimeGenerated, 15m) - | where count_distinct_OfficeObjectId >= threshold - | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), - AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // OfficeActivity - Frequent User Agents + let FrequentUAOffice = BaseeventsOffice + | summarize FUACount = count() by UserAgent, RecordType, Operation + | where FUACount >= threshold + | distinct UserAgent; + + // OfficeActivity - User Baseline + let UserBaseLineOffice = BaseeventsOffice + | summarize Count = count() by UserId, Operation, Site_Url + | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url; + + // OfficeActivity - Recent User Activity + let RecentActivityOffice = OfficeActivity + | where TimeGenerated > ago(endtime) + | where RecordType =~ szSharePointFileOperation + | where Operation in~ (szOperations) + | where isnotempty(UserAgent) + | where UserAgent in~ (FrequentUAOffice) + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OfficeObjectIdCount = dcount(OfficeObjectId), OfficeObjectIdList = make_set(OfficeObjectId), UserAgentSeenCount = count() + by RecordType, Operation, UserAgent, UserType, UserId, ClientIP, OfficeWorkload, Site_Url; + + // EnrichedMicrosoft365AuditLogs - Base Events + let BaseeventsEnriched = EnrichedMicrosoft365AuditLogs + | where TimeGenerated between (ago(starttime)..ago(endtime)) + | where RecordType == szSharePointFileOperation + | where Operation in (szOperations) + | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) + | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) + | where isnotempty(UserAgent); + + // EnrichedMicrosoft365AuditLogs - Frequent User Agents + let FrequentUAEnriched = BaseeventsEnriched + | summarize FUACount = count() by UserAgent, RecordType, Operation + | where FUACount >= threshold + | distinct UserAgent; + + // EnrichedMicrosoft365AuditLogs - User Baseline + let UserBaseLineEnriched = BaseeventsEnriched + | summarize Count = count() by UserId, Operation, Site_Url + | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url; + + // EnrichedMicrosoft365AuditLogs - Recent User Activity + let RecentActivityEnriched = EnrichedMicrosoft365AuditLogs + | where TimeGenerated > ago(endtime) + | where RecordType == szSharePointFileOperation + | where Operation in (szOperations) + | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) + | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) + | where isnotempty(UserAgent) + | where UserAgent in (FrequentUAEnriched) + | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ObjectIdCount = dcount(ObjectId), ObjectIdList = make_set(ObjectId), UserAgentSeenCount = count() + by RecordType, Operation, UserAgent, UserId, ClientIP, Site_Url; + + // Combine Baseline and Recent Activity, Calculate Deviation, and Deduplicate + let UserBehaviorAnalysisOffice = UserBaseLineOffice + | join kind=inner (RecentActivityOffice) on UserId, Operation, Site_Url + | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount; + + let UserBehaviorAnalysisEnriched = UserBaseLineEnriched + | join kind=inner (RecentActivityEnriched) on UserId, Operation, Site_Url + | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount; // Combine Office and Enriched Logs - let CombinedEvents = OfficeEvents - | union EnrichedEvents - | summarize arg_min(TimeGenerated, *) by UserId, ClientIP; + let CombinedUserBehaviorAnalysis = UserBehaviorAnalysisOffice + | union UserBehaviorAnalysisEnriched; - // Final Output - CombinedEvents - | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist - | order by TimeGenerated desc + // Filter and Format Final Results + CombinedUserBehaviorAnalysis + | where Deviation > 0.25 + | extend UserIdName = tostring(split(UserId, '@')[0]), + UserIdUPNSuffix = tostring(split(UserId, '@')[1]) + | project-reorder StartTimeUtc, EndTimeUtc, UserAgent, UserAgentSeenCount, UserId, ClientIP, Site_Url + | order by UserAgentSeenCount desc, UserAgent asc, UserId asc, Site_Url asc entityMappings: - entityType: Account fieldMappings: - identifier: FullName columnName: UserId - identifier: Name - columnName: AccountName + columnName: UserIdName - identifier: UPNSuffix - columnName: AccountUPNSuffix + columnName: UserIdUPNSuffix - entityType: IP fieldMappings: - identifier: Address columnName: ClientIP - - entityType: File + - entityType: URL fieldMappings: - - identifier: Name - columnName: FileSample -customDetails: - TransferCount: count_distinct_ObjectId - FilesList: fileslist -incidentConfiguration: - createIncident: true - groupingConfiguration: - enabled: true - reopenClosedIncident: false - lookbackDuration: 5h - matchingMethod: Selected - groupByEntities: - - Account - groupByAlertDetails: [] - groupByCustomDetails: [] -version: 2.0.7 + - identifier: Url + columnName: Site_Url +version: 2.2.6 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml index eb68d9e29b..81410ac5c8 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml @@ -17,33 +17,39 @@ tactics: relevantTechniques: - T1020 query: | - let threshold = 5000; - // EnrichedMicrosoft365AuditLogs Query - let EnrichedEvents = EnrichedMicrosoft365AuditLogs - | where Workload has_any("SharePoint", "OneDrive") - | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | summarize count_distinct_ObjectId = dcount(ObjectId), fileslist = make_set(ObjectId, 10000) by UserId, ClientIP = ClientIp, bin(TimeGenerated, 15m) - | where count_distinct_ObjectId >= threshold - | extend UserAgent = tostring(AdditionalProperties.UserAgent) - | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // OfficeActivity Query - let OfficeEvents = OfficeActivity - | where EventSource == "SharePoint" - | where OfficeWorkload has_any("SharePoint", "OneDrive") - | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | summarize count_distinct_OfficeObjectId = dcount(OfficeObjectId), fileslist = make_set(OfficeObjectId, 10000) by UserId, ClientIP, UserAgent, bin(TimeGenerated, 15m) - | where count_distinct_OfficeObjectId >= threshold - | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // Combine Office and Enriched Logs - let CombinedEvents = OfficeEvents - | union EnrichedEvents - | summarize arg_min(TimeGenerated, *) by UserId, ClientIP, UserAgent; - // Final Output - CombinedEvents - | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist - | order by TimeGenerated desc + let threshold = 5000; + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload has_any("SharePoint", "OneDrive") + | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | extend UserAgent = tostring(AdditionalProperties["UserAgent"]) + | summarize count_distinct_ObjectId = dcount(ObjectId), + fileslist = make_set(ObjectId, 10000), + any_UserAgent = any(UserAgent) + by UserId, ClientIP = ClientIp, bin(TimeGenerated, 15m) + | where count_distinct_ObjectId >= threshold + | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where EventSource == "SharePoint" + | where OfficeWorkload has_any("SharePoint", "OneDrive") + | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | summarize count_distinct_OfficeObjectId = dcount(OfficeObjectId), + fileslist = make_set(OfficeObjectId, 10000), + any_UserAgent = any(UserAgent) + by UserId, ClientIP, bin(TimeGenerated, 15m) + | where count_distinct_OfficeObjectId >= threshold + | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(TimeGenerated, *) by UserId, ClientIP, any_UserAgent; + // Final Output + CombinedEvents + | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, UserAgent = any_UserAgent, count_distinct_ObjectId = coalesce(count_distinct_ObjectId, count_distinct_OfficeObjectId), fileslist + | order by TimeGenerated desc entityMappings: - entityType: Account fieldMappings: From a826a48e894340a006e2abaf508a44762d33f4d1 Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Wed, 2 Oct 2024 12:48:51 +0300 Subject: [PATCH 09/27] ClientIP issues --- .../Office 365 - SharePoint_Downloads_byNewUserAgent.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml index 4508159a51..0009e9068a 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml @@ -49,7 +49,7 @@ query: | | where isnotempty(UserAgent) | where UserAgent in~ (FrequentUAOffice) | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OfficeObjectIdCount = dcount(OfficeObjectId), OfficeObjectIdList = make_set(OfficeObjectId), UserAgentSeenCount = count() - by RecordType, Operation, UserAgent, UserType, UserId, ClientIP, OfficeWorkload, Site_Url; + by RecordType, Operation, UserAgent, UserType, UserId, ClientIP = ClientIp, OfficeWorkload, Site_Url; // EnrichedMicrosoft365AuditLogs - Base Events let BaseeventsEnriched = EnrichedMicrosoft365AuditLogs @@ -81,7 +81,7 @@ query: | | where isnotempty(UserAgent) | where UserAgent in (FrequentUAEnriched) | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ObjectIdCount = dcount(ObjectId), ObjectIdList = make_set(ObjectId), UserAgentSeenCount = count() - by RecordType, Operation, UserAgent, UserId, ClientIP, Site_Url; + by RecordType, Operation, UserAgent, UserId, ClientIP = ClientIp, Site_Url; // Combine Baseline and Recent Activity, Calculate Deviation, and Deduplicate let UserBehaviorAnalysisOffice = UserBaseLineOffice From 377f1854e2af1e6f630e6aa161dda745e002de67 Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Wed, 2 Oct 2024 13:12:10 +0300 Subject: [PATCH 10/27] KQL fix --- .../Office 365 - SharePoint_Downloads_byNewUserAgent.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml index 0009e9068a..c9d57bdca8 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml @@ -49,7 +49,7 @@ query: | | where isnotempty(UserAgent) | where UserAgent in~ (FrequentUAOffice) | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OfficeObjectIdCount = dcount(OfficeObjectId), OfficeObjectIdList = make_set(OfficeObjectId), UserAgentSeenCount = count() - by RecordType, Operation, UserAgent, UserType, UserId, ClientIP = ClientIp, OfficeWorkload, Site_Url; + by RecordType, Operation, UserAgent, UserType, UserId, ClientIP , OfficeWorkload, Site_Url; // EnrichedMicrosoft365AuditLogs - Base Events let BaseeventsEnriched = EnrichedMicrosoft365AuditLogs @@ -78,10 +78,11 @@ query: | | where Operation in (szOperations) | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) + | extend ClientIP = ClientIp | where isnotempty(UserAgent) | where UserAgent in (FrequentUAEnriched) | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ObjectIdCount = dcount(ObjectId), ObjectIdList = make_set(ObjectId), UserAgentSeenCount = count() - by RecordType, Operation, UserAgent, UserId, ClientIP = ClientIp, Site_Url; + by RecordType, Operation, UserAgent, UserId,ClientIP, Site_Url; // Combine Baseline and Recent Activity, Calculate Deviation, and Deduplicate let UserBehaviorAnalysisOffice = UserBaseLineOffice From 69d2f1f46d3ea80a6a1a7a25a042e4d72c351250 Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Wed, 2 Oct 2024 13:35:17 +0300 Subject: [PATCH 11/27] Hunting queries --- ...repoint_file_transfer_above_threshold.yaml | 2 +- ...omolousUserAccessingOtherUsersMailbox.yaml | 113 +++++++++++------- .../ExternalUserFromNewOrgAddedToTeams.yaml | 80 +++++++++---- ...direct_via_ExO_transport_rule_hunting.yaml | 63 +++++++--- .../Hunting Queries/MultiTeamBot.yaml | 53 +++++--- .../Hunting Queries/MultiTeamOwner.yaml | 78 ++++++++---- .../Hunting Queries/MultipleTeamsDeletes.yaml | 64 +++++++--- ...eUsersEmailForwardedToSameDestination.yaml | 78 ++++++++---- .../Hunting Queries/NewBotAddedToTeams.yaml | 58 ++++++--- ...ReservedFileNamesOnOfficeFileServices.yaml | 110 +++++++++++------ .../OfficeMailForwarding_hunting.yaml | 106 ++++++++-------- .../Hunting Queries/TeamsFilesUploaded.yaml | 71 +++++++---- .../UserAddToTeamsAndUploadsFile.yaml | 61 +++++++--- ...ReservedFileNamesOnOfficeFileServices.yaml | 63 ++++++---- .../new_sharepoint_downloads_by_IP.yaml | 61 ++++++---- ...new_sharepoint_downloads_by_UserAgent.yaml | 72 ++++++----- .../nonowner_MailboxLogin.yaml | 44 +++++-- ...powershell_or_nonbrowser_MailboxLogin.yaml | 51 +++++--- .../Hunting Queries/sharepoint_downloads.yaml | 68 +++++++---- 19 files changed, 865 insertions(+), 431 deletions(-) diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml index 1fc244436a..43db365da9 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml @@ -25,7 +25,7 @@ query: | | where Workload has_any("SharePoint", "OneDrive") | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") | summarize count_distinct_ObjectId = dcount(ObjectId), fileslist = make_set(ObjectId, 10000) - by UserId, ClientIP = ClientIp, bin(TimeGenerated, 15m) + by UserId, ClientIP, bin(TimeGenerated, 15m) | where count_distinct_ObjectId >= threshold | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) | extend AccountName = tostring(split(UserId, "@")[0]), diff --git a/Solutions/Global Secure Access/Hunting Queries/AnomolousUserAccessingOtherUsersMailbox.yaml b/Solutions/Global Secure Access/Hunting Queries/AnomolousUserAccessingOtherUsersMailbox.yaml index 5a5e54f363..d32cfda704 100644 --- a/Solutions/Global Secure Access/Hunting Queries/AnomolousUserAccessingOtherUsersMailbox.yaml +++ b/Solutions/Global Secure Access/Hunting Queries/AnomolousUserAccessingOtherUsersMailbox.yaml @@ -1,5 +1,5 @@ id: 271e8881-3044-4332-a5f4-42264c2e0315 -name: Anomalous access to other users' mailboxes +name: GSA Enriched Office 365 - Anomalous access to other users' mailboxes description: | 'Looks for users accessing multiple other users' mailboxes or accessing multiple folders in another users mailbox.' requiredDataConnectors: @@ -14,44 +14,77 @@ tags: - Solorigate - NOBELIUM query: | - let starttime = todatetime('{{StartTimeISO}}'); - let endtime = todatetime('{{EndTimeISO}}'); - let lookback = totimespan((endtime - starttime) * 2); - // Adjust this value to alter how many mailbox (other than their own) a user needs to access before being included in results - let user_threshold = 1; - // Adjust this value to alter how many mailbox folders in other's email accounts a users needs to access before being included in results. - let folder_threshold = 5; - // Exclude historical as known good (set lookback and timeframe to same value to skip this) - EnrichedMicrosoft365AuditLogs - | where TimeGenerated between (ago(lookback)..starttime) - | where Operation =~ "MailItemsAccessed" - | where ResultStatus =~ "Succeeded" - | extend MailboxOwnerUPN = tostring(parse_json(AdditionalProperties).MailboxOwnerUPN) - | where tolower(MailboxOwnerUPN) != tolower(UserId) - | join kind=rightanti ( - EnrichedMicrosoft365AuditLogs - | where TimeGenerated between (starttime..endtime) - | where Operation =~ "MailItemsAccessed" - | where ResultStatus =~ "Succeeded" - | extend MailboxOwnerUPN = tostring(parse_json(AdditionalProperties).MailboxOwnerUPN) - | where tolower(MailboxOwnerUPN) != tolower(UserId) - ) on MailboxOwnerUPN, UserId - | where isnotempty(tostring(parse_json(AdditionalProperties).Folders)) - | mv-expand Folders = parse_json(AdditionalProperties).Folders - | extend folders = tostring(Folders.Path) - | extend ClientIP = iif(ClientIp startswith "[", extract("\\[([^\\]]*)", 1, ClientIp), ClientIp) - | extend ClientInfoString = tostring(parse_json(AdditionalProperties).ClientInfoString) - | extend MailboxGuid = tostring(parse_json(AdditionalProperties).MailboxGuid) - | summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), set_folders = make_set(folders, 100000), set_ClientInfoString = make_set(ClientInfoString, 100000), set_ClientIP = make_set(ClientIP, 100000), set_MailboxGuid = make_set(MailboxGuid, 100000), set_MailboxOwnerUPN = make_set(MailboxOwnerUPN, 100000) by UserId - | extend folder_count = array_length(set_folders) - | extend user_count = array_length(set_MailboxGuid) - | where user_count > user_threshold or folder_count > folder_threshold - | extend Reason = case(user_count > user_threshold and folder_count > folder_threshold, "Both User and Folder Threshold Exceeded", folder_count > folder_threshold and user_count < user_threshold, "Folder Count Threshold Exceeded", "User Threshold Exceeded") - | sort by user_count desc - | project-reorder UserId, user_count, folder_count, set_MailboxOwnerUPN, set_ClientIP, set_ClientInfoString, set_folders - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) - | extend Account_0_Name = AccountName - | extend Account_0_UPNSuffix = AccountUPNSuffix + let starttime = todatetime('{{StartTimeISO}}'); + let endtime = todatetime('{{EndTimeISO}}'); + let lookback = totimespan((endtime - starttime) * 2); + let user_threshold = 1; // Threshold for number of mailboxes accessed + let folder_threshold = 5; // Threshold for number of mailbox folders accessed + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where TimeGenerated between (ago(lookback)..starttime) + | where Operation =~ "MailItemsAccessed" + | where ResultStatus =~ "Succeeded" + | where tolower(MailboxOwnerUPN) != tolower(UserId) + | join kind=rightanti ( + OfficeActivity + | where TimeGenerated between (starttime..endtime) + | where Operation =~ "MailItemsAccessed" + | where ResultStatus =~ "Succeeded" + | where tolower(MailboxOwnerUPN) != tolower(UserId) + ) on MailboxOwnerUPN, UserId + | where isnotempty(Folders) + | mv-expand parse_json(Folders) + | extend folders = tostring(Folders.Path) + | extend ClientIP = iif(Client_IPAddress startswith "[", extract("\\[([^\\]]*)", 1, Client_IPAddress), Client_IPAddress) + | summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), set_folders = make_set(folders, 100000), set_ClientInfoString = make_set(ClientInfoString, 100000), set_ClientIP = make_set(ClientIP, 100000), set_MailboxGuid = make_set(MailboxGuid, 100000), set_MailboxOwnerUPN = make_set(MailboxOwnerUPN, 100000) by UserId + | extend folder_count = array_length(set_folders) + | extend user_count = array_length(set_MailboxGuid) + | where user_count > user_threshold or folder_count > folder_threshold + | extend Reason = case(user_count > user_threshold and folder_count > folder_threshold, "Both User and Folder Threshold Exceeded", folder_count > folder_threshold and user_count < user_threshold, "Folder Count Threshold Exceeded", "User Threshold Exceeded") + | sort by user_count desc + | project-reorder UserId, user_count, folder_count, set_MailboxOwnerUPN, set_ClientIP, set_ClientInfoString, set_folders + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend Account_0_Name = AccountName + | extend Account_0_UPNSuffix = AccountUPNSuffix; + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where TimeGenerated between (ago(lookback)..starttime) + | where Operation =~ "MailItemsAccessed" + | where ResultStatus =~ "Succeeded" + | extend MailboxOwnerUPN = tostring(parse_json(AdditionalProperties).MailboxOwnerUPN) + | where tolower(MailboxOwnerUPN) != tolower(UserId) + | join kind=rightanti ( + EnrichedMicrosoft365AuditLogs + | where TimeGenerated between (starttime..endtime) + | where Operation =~ "MailItemsAccessed" + | where ResultStatus =~ "Succeeded" + | extend MailboxOwnerUPN = tostring(parse_json(AdditionalProperties).MailboxOwnerUPN) + | where tolower(MailboxOwnerUPN) != tolower(UserId) + ) on MailboxOwnerUPN, UserId + | where isnotempty(tostring(parse_json(AdditionalProperties).Folders)) + | mv-expand Folders = parse_json(AdditionalProperties).Folders + | extend folders = tostring(Folders.Path) + | extend ClientIP = iif(ClientIp startswith "[", extract("\\[([^\\]]*)", 1, ClientIp), ClientIp) + | extend ClientInfoString = tostring(parse_json(AdditionalProperties).ClientInfoString) + | extend MailboxGuid = tostring(parse_json(AdditionalProperties).MailboxGuid) + | summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), set_folders = make_set(folders, 100000), set_ClientInfoString = make_set(ClientInfoString, 100000), set_ClientIP = make_set(ClientIP, 100000), set_MailboxGuid = make_set(MailboxGuid, 100000), set_MailboxOwnerUPN = make_set(MailboxOwnerUPN, 100000) by UserId + | extend folder_count = array_length(set_folders) + | extend user_count = array_length(set_MailboxGuid) + | where user_count > user_threshold or folder_count > folder_threshold + | extend Reason = case(user_count > user_threshold and folder_count > folder_threshold, "Both User and Folder Threshold Exceeded", folder_count > folder_threshold and user_count < user_threshold, "Folder Count Threshold Exceeded", "User Threshold Exceeded") + | sort by user_count desc + | project-reorder UserId, user_count, folder_count, set_MailboxOwnerUPN, set_ClientIP, set_ClientInfoString, set_folders + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend Account_0_Name = AccountName + | extend Account_0_UPNSuffix = AccountUPNSuffix; + // Combine Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(StartTime, *) by UserId, ClientIP; + // Final Output + CombinedEvents + | project UserId, user_count, folder_count, set_MailboxOwnerUPN, set_ClientIP, set_ClientInfoString, set_folders, AccountName, AccountUPNSuffix + | order by user_count desc entityMappings: - entityType: Account fieldMappings: @@ -59,4 +92,4 @@ entityMappings: columnName: AccountName - identifier: UPNSuffix columnName: AccountUPNSuffix -version: 2.0.1 +version: 2.0.2 diff --git a/Solutions/Global Secure Access/Hunting Queries/ExternalUserFromNewOrgAddedToTeams.yaml b/Solutions/Global Secure Access/Hunting Queries/ExternalUserFromNewOrgAddedToTeams.yaml index 9cee2439f7..a76746f8b8 100644 --- a/Solutions/Global Secure Access/Hunting Queries/ExternalUserFromNewOrgAddedToTeams.yaml +++ b/Solutions/Global Secure Access/Hunting Queries/ExternalUserFromNewOrgAddedToTeams.yaml @@ -1,5 +1,5 @@ id: 6fce5baf-bfc2-4c56-a6b7-9c4733fc5a45 -name: External user from a new organisation added to Teams +name: GSA Enriched Office 365 - External user from a new organisation added to Teams description: | 'This query identifies external users added to Teams where the user's domain is not one previously seen in Teams data.' requiredDataConnectors: @@ -14,30 +14,60 @@ query: | let starttime = todatetime('{{StartTimeISO}}'); let endtime = todatetime('{{EndTimeISO}}'); let lookback = totimespan((endtime - starttime) * 7); - let known_orgs = ( - EnrichedMicrosoft365AuditLogs - | where TimeGenerated between (ago(lookback) .. starttime) - | where Workload == "MicrosoftTeams" - | where Operation in ("MemberAdded", "TeamsSessionStarted") - // Extract the correct UPN and parse our external organization domain - | extend Members = parse_json(tostring(AdditionalProperties.Members)) - | extend UPN = iif(Operation == "MemberAdded", tostring(Members[0].UPN), UserId) - | extend Organization = tostring(split(split(UPN, "_")[1], "#")[0]) - | where isnotempty(Organization) - | summarize by Organization + // OfficeActivity Known Organizations + let known_orgs_office = ( + OfficeActivity + | where TimeGenerated between(ago(lookback)..starttime) + | where OfficeWorkload =~ "MicrosoftTeams" + | where Operation =~ "MemberAdded" or Operation =~ "TeamsSessionStarted" + | extend UPN = iif(Operation == "MemberAdded", tostring(Members[0].UPN), UserId) + | extend Organization = tostring(split(split(UPN, "_")[1], "#")[0]) + | where isnotempty(Organization) + | summarize by Organization ); - EnrichedMicrosoft365AuditLogs - | where TimeGenerated between (starttime .. endtime) - | where Workload == "MicrosoftTeams" - | where Operation == "MemberAdded" - | extend Members = parse_json(tostring(AdditionalProperties.Members)) - | extend UPN = tostring(Members[0].UPN) - | extend Organization = tostring(split(split(UPN, "_")[1], "#")[0]) - | where isnotempty(Organization) - | where Organization !in (known_orgs) - | extend AccountName = tostring(split(UPN, "@")[0]), AccountUPNSuffix = tostring(split(UPN, "@")[1]) - | extend Account_0_Name = AccountName - | extend Account_0_UPNSuffix = AccountUPNSuffix + // OfficeActivity Query for New Organizations + let OfficeEvents = OfficeActivity + | where TimeGenerated between(starttime..endtime) + | where OfficeWorkload =~ "MicrosoftTeams" + | where Operation =~ "MemberAdded" + | extend UPN = tostring(parse_json(Members)[0].UPN) + | extend Organization = tostring(split(split(UPN, "_")[1], "#")[0]) + | where isnotempty(Organization) + | where Organization !in (known_orgs_office) + | extend AccountName = tostring(split(UPN, "@")[0]), AccountUPNSuffix = tostring(split(UPN, "@")[1]) + | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix; + // EnrichedMicrosoft365AuditLogs Known Organizations + let known_orgs_enriched = ( + EnrichedMicrosoft365AuditLogs + | where TimeGenerated between(ago(lookback)..starttime) + | where Workload == "MicrosoftTeams" + | where Operation in ("MemberAdded", "TeamsSessionStarted") + | extend Members = parse_json(tostring(AdditionalProperties.Members)) + | extend UPN = iif(Operation == "MemberAdded", tostring(Members[0].UPN), UserId) + | extend Organization = tostring(split(split(UPN, "_")[1], "#")[0]) + | where isnotempty(Organization) + | summarize by Organization + ); + // EnrichedMicrosoft365AuditLogs Query for New Organizations + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where TimeGenerated between(starttime..endtime) + | where Workload == "MicrosoftTeams" + | where Operation == "MemberAdded" + | extend Members = parse_json(tostring(AdditionalProperties.Members)) + | extend UPN = tostring(Members[0].UPN) + | extend Organization = tostring(split(split(UPN, "_")[1], "#")[0]) + | where isnotempty(Organization) + | where Organization !in (known_orgs_enriched) + | extend AccountName = tostring(split(UPN, "@")[0]), AccountUPNSuffix = tostring(split(UPN, "@")[1]) + | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix; + // Combine Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(TimeGenerated, *) by Organization, UPN; + // Final Output + CombinedEvents + | project Organization, UPN, AccountName, AccountUPNSuffix + | order by Organization asc entityMappings: - entityType: Account fieldMappings: @@ -45,4 +75,4 @@ entityMappings: columnName: AccountName - identifier: UPNSuffix columnName: AccountUPNSuffix -version: 2.0.1 +version: 2.0.2 diff --git a/Solutions/Global Secure Access/Hunting Queries/Mail_redirect_via_ExO_transport_rule_hunting.yaml b/Solutions/Global Secure Access/Hunting Queries/Mail_redirect_via_ExO_transport_rule_hunting.yaml index 94de9dade2..43c3385545 100644 --- a/Solutions/Global Secure Access/Hunting Queries/Mail_redirect_via_ExO_transport_rule_hunting.yaml +++ b/Solutions/Global Secure Access/Hunting Queries/Mail_redirect_via_ExO_transport_rule_hunting.yaml @@ -1,5 +1,5 @@ id: 9891684a-1e3a-4546-9403-3439513cbc70 -name: Mail Redirect via ExO Transport Rule +name: GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule description: | Identifies when Exchange Online transport rule is configured to forward emails. This could be an adversary mailbox configured to collect mail from multiple user accounts. @@ -14,20 +14,51 @@ relevantTechniques: - T1114 - T1020 query: | - EnrichedMicrosoft365AuditLogs - | where Workload == "Exchange" - | where Operation in ("New-TransportRule", "Set-TransportRule") - | mv-apply DynamicParameters = todynamic(AdditionalProperties.Parameters) on (summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))) - | extend RuleName = case( - Operation == "Set-TransportRule", ObjectId, - Operation == "New-TransportRule", ParsedParameters.Name, - "Unknown") - | mv-expand ExpandedParameters = todynamic(AdditionalProperties.Parameters) - | where ExpandedParameters.Name in ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) - | extend RedirectTo = ExpandedParameters.Value - | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIp)[0] - | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, Operation, RuleName, AdditionalProperties - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload == "Exchange" + | where Operation in ("New-TransportRule", "Set-TransportRule") + | mv-apply DynamicParameters = todynamic(AdditionalProperties.Parameters) on ( + summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) + ) + | extend RuleName = case( + Operation == "Set-TransportRule", ObjectId, + Operation == "New-TransportRule", ParsedParameters.Name, + "Unknown" + ) + | mv-expand ExpandedParameters = todynamic(AdditionalProperties.Parameters) + | where ExpandedParameters.Name in ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) + | extend RedirectTo = ExpandedParameters.Value + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIp)[0] + | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, Operation, RuleName, AdditionalProperties + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where OfficeWorkload == "Exchange" + | where Operation in ("New-TransportRule", "Set-TransportRule") + | mv-apply DynamicParameters = todynamic(Parameters) on ( + summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) + ) + | extend RuleName = case( + Operation == "Set-TransportRule", OfficeObjectId, + Operation == "New-TransportRule", ParsedParameters.Name, + "Unknown" + ) + | mv-expand ExpandedParameters = todynamic(Parameters) + | where ExpandedParameters.Name in ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) + | extend RedirectTo = ExpandedParameters.Value + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] + | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, Operation, RuleName, Parameters + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, IP_0_Address = IPAddress; + // Combine Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(TimeGenerated, *) by RuleName, UserId; + // Final Output + CombinedEvents + | project TimeGenerated, RuleName, RedirectTo, IPAddress, Port, UserId, AccountName, AccountUPNSuffix + | order by TimeGenerated desc entityMappings: - entityType: Account fieldMappings: @@ -39,4 +70,4 @@ entityMappings: fieldMappings: - identifier: Address columnName: IPAddress -version: 2.0.1 +version: 2.0.2 diff --git a/Solutions/Global Secure Access/Hunting Queries/MultiTeamBot.yaml b/Solutions/Global Secure Access/Hunting Queries/MultiTeamBot.yaml index b150a46ba9..6f2790c0c1 100644 --- a/Solutions/Global Secure Access/Hunting Queries/MultiTeamBot.yaml +++ b/Solutions/Global Secure Access/Hunting Queries/MultiTeamBot.yaml @@ -1,5 +1,5 @@ id: 9eb64924-ec8d-44d0-b1f2-10665150fb74 -name: Bots added to multiple teams +name: GSA Enriched Office 365 - Bots added to multiple teams description: | 'This hunting query helps identify bots added to multiple Teams in a short space of time.' requiredDataConnectors: @@ -13,22 +13,41 @@ relevantTechniques: - T1176 - T1119 query: | - // Adjust these thresholds to suit your environment. - let threshold = 2; - let time_threshold = timespan(5m); - EnrichedMicrosoft365AuditLogs - | where Workload == "MicrosoftTeams" - | where Operation == "BotAddedToTeam" - | extend TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName) - | summarize Start = max(TimeGenerated), End = min(TimeGenerated), Teams = make_set(TeamName, 10000) by UserId - | extend CountOfTeams = array_length(Teams) - | extend TimeDelta = End - Start - | where CountOfTeams > threshold - | where TimeDelta <= time_threshold - | project Start, End, Teams, CountOfTeams, UserId - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) - | extend Account_0_Name = AccountName - | extend Account_0_UPNSuffix = AccountUPNSuffix + let threshold = 2; // Adjust this threshold based on your environment + let time_threshold = timespan(5m); // Adjust the time delta threshold + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where OfficeWorkload =~ "MicrosoftTeams" + | where Operation =~ "BotAddedToTeam" + | summarize Start = max(TimeGenerated), End = min(TimeGenerated), Teams = make_set(TeamName, 10000) by UserId + | extend CountOfTeams = array_length(Teams) + | extend TimeDelta = End - Start + | where CountOfTeams > threshold + | where TimeDelta >= time_threshold + | project Start, End, Teams, CountOfTeams, UserId + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix; + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload == "MicrosoftTeams" + | where Operation == "BotAddedToTeam" + | extend TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName) + | summarize Start = max(TimeGenerated), End = min(TimeGenerated), Teams = make_set(TeamName, 10000) by UserId + | extend CountOfTeams = array_length(Teams) + | extend TimeDelta = End - Start + | where CountOfTeams > threshold + | where TimeDelta <= time_threshold + | project Start, End, Teams, CountOfTeams, UserId + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix; + // Combine Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(Start, *) by UserId; + // Final Output + CombinedEvents + | project Start, End, Teams, CountOfTeams, UserId, AccountName, AccountUPNSuffix + | order by Start desc entityMappings: - entityType: Account fieldMappings: diff --git a/Solutions/Global Secure Access/Hunting Queries/MultiTeamOwner.yaml b/Solutions/Global Secure Access/Hunting Queries/MultiTeamOwner.yaml index 637e316c51..ef14dc12b7 100644 --- a/Solutions/Global Secure Access/Hunting Queries/MultiTeamOwner.yaml +++ b/Solutions/Global Secure Access/Hunting Queries/MultiTeamOwner.yaml @@ -1,5 +1,5 @@ id: 558f15dd-3171-4b11-bf24-31c0610a20e0 -name: User made Owner of multiple teams +name: GSA Enriched Office 365 - User made Owner of multiple teams description: | This hunting query identifies users who have been made Owner of multiple Teams. requiredDataConnectors: @@ -11,28 +11,58 @@ tactics: relevantTechniques: - T1078 query: | - // Adjust this value to change how many teams a user is made owner of before detecting - let max_owner_count = 3; - // Identify users who have been made owner of multiple Teams - let high_owner_count = ( - EnrichedMicrosoft365AuditLogs - | where Workload == "MicrosoftTeams" - | where Operation == "MemberRoleChanged" - | extend Member = tostring(UserId) - | extend NewRole = toint(parse_json(tostring(AdditionalProperties)).Role) - | where NewRole == 2 - | summarize TeamCount = dcount(ObjectId) by Member - | where TeamCount > max_owner_count - | project Member - ); - EnrichedMicrosoft365AuditLogs - | where Workload == "MicrosoftTeams" - | where Operation == "MemberRoleChanged" - | extend Member = tostring(UserId) - | extend NewRole = toint(parse_json(tostring(AdditionalProperties)).Role) - | where NewRole == 2 - | where Member in (high_owner_count) - | extend AccountName = tostring(split(Member, "@")[0]), AccountUPNSuffix = tostring(split(Member, "@")[1]) + let max_owner_count = 3; + // Adjust this value to change how many teams a user is made owner of before detecting + // OfficeActivity Query: Identify users who have been made owners of more than 'max_owner_count' teams + let high_owner_count_office = ( + OfficeActivity + | where OfficeWorkload =~ "MicrosoftTeams" + | where Operation =~ "MemberRoleChanged" + | extend Member = tostring(parse_json(Members)[0].UPN) + | extend NewRole = toint(parse_json(Members)[0].Role) + | where NewRole == 2 // Role 2 corresponds to "Owner" + | summarize TeamCount = dcount(TeamName) by Member + | where TeamCount > max_owner_count + | project Member + ); + // OfficeActivity Query: Fetch details for users with high ownership count + let OfficeEvents = OfficeActivity + | where OfficeWorkload =~ "MicrosoftTeams" + | where Operation =~ "MemberRoleChanged" + | extend Member = tostring(parse_json(Members)[0].UPN) + | extend NewRole = toint(parse_json(Members)[0].Role) + | where NewRole == 2 // Role 2 corresponds to "Owner" + | where Member in (high_owner_count_office) + | extend AccountName = tostring(split(Member, "@")[0]), AccountUPNSuffix = tostring(split(Member, "@")[1]); + // EnrichedMicrosoft365AuditLogs Query: Identify users who have been made owners of more than 'max_owner_count' teams + let high_owner_count_enriched = ( + EnrichedMicrosoft365AuditLogs + | where Workload == "MicrosoftTeams" + | where Operation == "MemberRoleChanged" + | extend Member = tostring(UserId) + | extend NewRole = toint(parse_json(tostring(AdditionalProperties)).Role) + | where NewRole == 2 // Role 2 corresponds to "Owner" + | summarize TeamCount = dcount(ObjectId) by Member + | where TeamCount > max_owner_count + | project Member + ); + // EnrichedMicrosoft365AuditLogs Query: Fetch details for users with high ownership count + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload == "MicrosoftTeams" + | where Operation == "MemberRoleChanged" + | extend Member = tostring(UserId) + | extend NewRole = toint(parse_json(tostring(AdditionalProperties)).Role) + | where NewRole == 2 // Role 2 corresponds to "Owner" + | where Member in (high_owner_count_enriched) + | extend AccountName = tostring(split(Member, "@")[0]), AccountUPNSuffix = tostring(split(Member, "@")[1]); + // Combine Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize by Member, AccountName, AccountUPNSuffix; + // Final Output + CombinedEvents + | order by Member asc + | project Member, AccountName, AccountUPNSuffix entityMappings: - entityType: Account fieldMappings: @@ -40,5 +70,5 @@ entityMappings: columnName: AccountName - identifier: UPNSuffix columnName: AccountUPNSuffix -version: 2.0.1 +version: 2.0.2 kind: Scheduled diff --git a/Solutions/Global Secure Access/Hunting Queries/MultipleTeamsDeletes.yaml b/Solutions/Global Secure Access/Hunting Queries/MultipleTeamsDeletes.yaml index edc5adc288..a616206b7f 100644 --- a/Solutions/Global Secure Access/Hunting Queries/MultipleTeamsDeletes.yaml +++ b/Solutions/Global Secure Access/Hunting Queries/MultipleTeamsDeletes.yaml @@ -1,5 +1,5 @@ id: 64990414-b015-4edf-bef0-343b741e68c5 -name: Multiple Teams deleted by a single user +name: GSA Enriched Office 365 - Multiple Teams deleted by a single user description: | 'This hunting query identifies where multiple Teams have been deleted by a single user in a short timeframe.' requiredDataConnectors: @@ -12,23 +12,49 @@ relevantTechniques: - T1485 - T1489 query: | - // Adjust this value to change how many Teams should be deleted before including - let max_delete = 3; - let deleting_users = ( - EnrichedMicrosoft365AuditLogs - | where Workload == "MicrosoftTeams" - | where Operation == "TeamDeleted" - | summarize count_ = count() by UserId - | where count_ > max_delete - | project UserId - ); - EnrichedMicrosoft365AuditLogs - | where Workload == "MicrosoftTeams" - | where Operation == "TeamDeleted" - | where UserId in (deleting_users) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) - | extend Account_0_Name = AccountName - | extend Account_0_UPNSuffix = AccountUPNSuffix + let max_delete = 3; // Adjust this value to change how many Teams should be deleted before being included + // EnrichedMicrosoft365AuditLogs - Users who deleted more than 'max_delete' Teams + let deleting_users_enriched = ( + EnrichedMicrosoft365AuditLogs + | where Workload == "MicrosoftTeams" + | where Operation == "TeamDeleted" + | summarize count_ = count() by UserId + | where count_ > max_delete + | project UserId + ); + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload == "MicrosoftTeams" + | where Operation == "TeamDeleted" + | where UserId in (deleting_users_enriched) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix + | project TimeGenerated, UserId, Account_0_Name, Account_0_UPNSuffix; + // OfficeActivity - Users who deleted more than 'max_delete' Teams + let deleting_users_office = ( + OfficeActivity + | where OfficeWorkload =~ "MicrosoftTeams" + | where Operation =~ "TeamDeleted" + | summarize count_ = count() by UserId + | where count_ > max_delete + | project UserId + ); + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where OfficeWorkload =~ "MicrosoftTeams" + | where Operation =~ "TeamDeleted" + | where UserId in (deleting_users_office) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix + | project TimeGenerated, UserId, Account_0_Name, Account_0_UPNSuffix; + // Combine Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(TimeGenerated, *) by UserId; + // Final Output + CombinedEvents + | project TimeGenerated, UserId, Account_0_Name, Account_0_UPNSuffix + | order by TimeGenerated desc entityMappings: - entityType: Account fieldMappings: @@ -36,4 +62,4 @@ entityMappings: columnName: AccountName - identifier: UPNSuffix columnName: AccountUPNSuffix -version: 2.0.1 +version: 2.0.2 diff --git a/Solutions/Global Secure Access/Hunting Queries/MultipleUsersEmailForwardedToSameDestination.yaml b/Solutions/Global Secure Access/Hunting Queries/MultipleUsersEmailForwardedToSameDestination.yaml index 12d9310574..c18e91a8f3 100644 --- a/Solutions/Global Secure Access/Hunting Queries/MultipleUsersEmailForwardedToSameDestination.yaml +++ b/Solutions/Global Secure Access/Hunting Queries/MultipleUsersEmailForwardedToSameDestination.yaml @@ -1,5 +1,5 @@ id: a1551ae4-f61c-4bca-9c57-4d0d681db2e9 -name: Multiple Users Email Forwarded to Same Destination +name: GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination description: | Identifies when multiple (more than one) users' mailboxes are configured to forward to the same destination. This could be an attacker-controlled destination mailbox configured to collect mail from multiple compromised user accounts. @@ -22,25 +22,61 @@ relevantTechniques: query: | let queryfrequency = 1d; let queryperiod = 7d; - EnrichedMicrosoft365AuditLogs - | where TimeGenerated > ago(queryperiod) - | where Workload == "Exchange" - | where AdditionalProperties has_any ("ForwardTo", "RedirectTo", "ForwardingSmtpAddress") - | mv-apply DynamicParameters = todynamic(AdditionalProperties) on (summarize ParsedParameters = make_bag(bag_pack(tostring(DynamicParameters.Name), DynamicParameters.Value))) - | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source') - | extend DestinationMailAddress = tolower(case( - isnotempty(column_ifexists("ForwardTo", "")), column_ifexists("ForwardTo", ""), - isnotempty(column_ifexists("RedirectTo", "")), column_ifexists("RedirectTo", ""), - isnotempty(column_ifexists("ForwardingSmtpAddress", "")), trim_start(@"smtp:", column_ifexists("ForwardingSmtpAddress", "")), - "")) - | where isnotempty(DestinationMailAddress) - | mv-expand split(DestinationMailAddress, ";") - | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIp)[0] - | extend ClientIp = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]) - | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIp - | where DistinctUserCount > 1 and EndTime > ago(queryfrequency) - | mv-expand UserId to typeof(string) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where TimeGenerated > ago(queryperiod) + | where Workload == "Exchange" + | where AdditionalProperties has_any ("ForwardTo", "RedirectTo", "ForwardingSmtpAddress") + | mv-apply DynamicParameters = todynamic(AdditionalProperties) on ( + summarize ParsedParameters = make_bag(bag_pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) + ) + | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source') + | extend DestinationMailAddress = tolower(case( + isnotempty(column_ifexists("ForwardTo", "")), column_ifexists("ForwardTo", ""), + isnotempty(column_ifexists("RedirectTo", "")), column_ifexists("RedirectTo", ""), + isnotempty(column_ifexists("ForwardingSmtpAddress", "")), trim_start(@"smtp:", column_ifexists("ForwardingSmtpAddress", "")), + "" + )) + | where isnotempty(DestinationMailAddress) + | mv-expand split(DestinationMailAddress, ";") + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIp)[0] + | extend ClientIp = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]) + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIp + | where DistinctUserCount > 1 and EndTime > ago(queryfrequency) + | mv-expand UserId to typeof(string) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where TimeGenerated > ago(queryperiod) + | where OfficeWorkload =~ "Exchange" + | where Parameters has_any ("ForwardTo", "RedirectTo", "ForwardingSmtpAddress") + | mv-apply DynamicParameters = todynamic(Parameters) on ( + summarize ParsedParameters = make_bag(bag_pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) + ) + | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source') + | extend DestinationMailAddress = tolower(case( + isnotempty(column_ifexists("ForwardTo", "")), column_ifexists("ForwardTo", ""), + isnotempty(column_ifexists("RedirectTo", "")), column_ifexists("RedirectTo", ""), + isnotempty(column_ifexists("ForwardingSmtpAddress", "")), trim_start(@"smtp:", column_ifexists("ForwardingSmtpAddress", "")), + "" + )) + | where isnotempty(DestinationMailAddress) + | mv-expand split(DestinationMailAddress, ";") + | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] + | extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]) + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP + | where DistinctUserCount > 1 and EndTime > ago(queryfrequency) + | mv-expand UserId to typeof(string) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, IP_0_Address = ClientIP; + // Combine Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(StartTime, *) by DestinationMailAddress, ClientIp; + // Final Output + CombinedEvents + | project StartTime, EndTime, DestinationMailAddress, ClientIp, Ports, UserId, AccountName, AccountUPNSuffix + | order by StartTime desc entityMappings: - entityType: Account fieldMappings: @@ -52,5 +88,5 @@ entityMappings: fieldMappings: - identifier: Address columnName: ClientIp -version: 2.0.1 +version: 2.0.2 kind: Scheduled diff --git a/Solutions/Global Secure Access/Hunting Queries/NewBotAddedToTeams.yaml b/Solutions/Global Secure Access/Hunting Queries/NewBotAddedToTeams.yaml index 7e25db9f10..aef7ce0206 100644 --- a/Solutions/Global Secure Access/Hunting Queries/NewBotAddedToTeams.yaml +++ b/Solutions/Global Secure Access/Hunting Queries/NewBotAddedToTeams.yaml @@ -1,5 +1,5 @@ id: bf76e508-9282-4cf1-9cc1-5c20c3dea2ee -name: Previously Unseen Bot or Application Added to Teams +name: GSA Enriched Office 365 - Previously Unseen Bot or Application Added to Teams description: | This hunting query helps identify new, and potentially unapproved applications or bots being added to Teams. requiredDataConnectors: @@ -13,22 +13,44 @@ relevantTechniques: - T1176 - T1119 query: | - let starttime = todatetime('{{StartTimeISO}}'); - let endtime = todatetime('{{EndTimeISO}}'); - let lookback = starttime - 14d; - let historical_bots = - EnrichedMicrosoft365AuditLogs - | where TimeGenerated between (lookback .. starttime) - | where Workload == "MicrosoftTeams" - | extend AddonName = tostring(parse_json(tostring(AdditionalProperties)).AddonName) - | where isnotempty(AddonName) - | distinct AddonName; - EnrichedMicrosoft365AuditLogs - | where TimeGenerated between (starttime .. endtime) - | where Workload == "MicrosoftTeams" - | extend AddonName = tostring(parse_json(tostring(AdditionalProperties)).AddonName) - | where AddonName !in (historical_bots) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + let starttime = todatetime('{{StartTimeISO}}'); + let endtime = todatetime('{{EndTimeISO}}'); + let lookback = starttime - 14d; + // Historical bots from EnrichedMicrosoft365AuditLogs + let historical_bots_enriched = EnrichedMicrosoft365AuditLogs + | where TimeGenerated between (lookback .. starttime) + | where Workload == "MicrosoftTeams" + | extend AddonName = tostring(parse_json(tostring(AdditionalProperties)).AddonName) + | where isnotempty(AddonName) + | distinct AddonName; + // Historical bots from OfficeActivity + let historical_bots_office = OfficeActivity + | where TimeGenerated between (lookback .. starttime) + | where OfficeWorkload == "MicrosoftTeams" + | where isnotempty(AddonName) + | distinct AddonName; + // Find new bots in Enriched Logs + let new_bots_enriched = EnrichedMicrosoft365AuditLogs + | where TimeGenerated between (starttime .. endtime) + | where Workload == "MicrosoftTeams" + | extend AddonName = tostring(parse_json(tostring(AdditionalProperties)).AddonName) + | where AddonName !in (historical_bots_enriched) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Find new bots in OfficeActivity + let new_bots_office = OfficeActivity + | where TimeGenerated between (starttime .. endtime) + | where OfficeWorkload == "MicrosoftTeams" + | where isnotempty(AddonName) + | where AddonName !in (historical_bots_office) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine both new bots from Enriched Logs and OfficeActivity + let CombinedNewBots = new_bots_enriched + | union new_bots_office + | summarize arg_min(TimeGenerated, *) by AddonName, UserId; + // Final output + CombinedNewBots + | project TimeGenerated, AddonName, UserId, AccountName, AccountUPNSuffix + | order by TimeGenerated desc entityMappings: - entityType: Account fieldMappings: @@ -36,4 +58,4 @@ entityMappings: columnName: AccountName - identifier: UPNSuffix columnName: AccountUPNSuffix -version: 2.0.1 +version: 2.0.2 diff --git a/Solutions/Global Secure Access/Hunting Queries/New_WindowsReservedFileNamesOnOfficeFileServices.yaml b/Solutions/Global Secure Access/Hunting Queries/New_WindowsReservedFileNamesOnOfficeFileServices.yaml index 19250a6944..cb4b11c014 100644 --- a/Solutions/Global Secure Access/Hunting Queries/New_WindowsReservedFileNamesOnOfficeFileServices.yaml +++ b/Solutions/Global Secure Access/Hunting Queries/New_WindowsReservedFileNamesOnOfficeFileServices.yaml @@ -1,5 +1,5 @@ id: 641ecd2d-27c9-4f05-8433-8205096b09fc -name: New Windows Reserved Filenames staged on Office file services +name: GSA Enriched Office 365 - New Windows Reserved Filenames staged on Office file services description: | 'This identifies new Windows Reserved Filenames on Office services like SharePoint and OneDrive in the past 7 days. It also detects when a user uploads these files to another user's workspace, which may indicate malicious activity.' description-detailed: | @@ -18,43 +18,75 @@ tactics: relevantTechniques: - T1105 query: | - let starttime = todatetime('{{StartTimeISO}}'); - let endtime = todatetime('{{EndTimeISO}}'); - let lookback = totimespan((endtime - starttime) * 7); - let Reserved = dynamic(['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']); - EnrichedMicrosoft365AuditLogs - | where TimeGenerated between (starttime .. endtime) - | extend FileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName) - | extend ClientUserAgent = tostring(parse_json(tostring(AdditionalProperties)).ClientUserAgent) - | extend SiteUrl = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) - | where isnotempty(ObjectId) - | where ObjectId !~ FileName - | where ObjectId in (Reserved) or FileName in (Reserved) - | where ClientUserAgent !has "Mac OS" - | project TimeGenerated, Id, Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, ClientUserAgent, SiteUrl, ObjectId, FileName - | join kind=leftanti ( - EnrichedMicrosoft365AuditLogs - | where TimeGenerated between (ago(lookback) .. starttime) - | extend FileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName) - | extend ClientUserAgent = tostring(parse_json(tostring(AdditionalProperties)).ClientUserAgent) - | extend SiteUrl = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) - | where isnotempty(ObjectId) - | where ObjectId !~ FileName - | where ObjectId in (Reserved) or FileName in (Reserved) - | where ClientUserAgent !has "Mac OS" - | summarize PrevSeenCount = count() by ObjectId, UserId, FileName - ) on ObjectId - | extend SiteUrlUserFolder = tolower(split(SiteUrl, '/')[-2]) - | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\.', '_')) - | extend UserIdDiffThanUserFolder = iff(SiteUrl has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false) - | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(ClientUserAgent, 100000), - Ids = make_list(Id, 100000), SourceRelativeUrls = make_list(ObjectId, 100000), FileNames = make_list(FileName, 100000) - by Workload, RecordType, UserType, UserKey, UserId, ClientIp, SiteUrl, ObjectId, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) - | extend IP_0_Address = ClientIp - | extend Account_0_Name = AccountName - | extend Account_0_UPNSuffix = AccountUPNSuffix - | extend URL_0_Url = SiteUrl + let starttime = todatetime('{{StartTimeISO}}'); + let endtime = todatetime('{{EndTimeISO}}'); + let lookback = totimespan((endtime - starttime) * 7); + + // Reserved file names and extensions for Windows + let Reserved = dynamic(['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']); + + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where TimeGenerated between (starttime .. endtime) + | extend FileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName) + | extend ClientUserAgent = tostring(parse_json(tostring(AdditionalProperties)).ClientUserAgent) + | extend SiteUrl = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) + | where isnotempty(ObjectId) + | where ObjectId !~ FileName + | where ObjectId in (Reserved) or FileName in (Reserved) + | where ClientUserAgent !has "Mac OS" + | join kind=leftanti ( + EnrichedMicrosoft365AuditLogs + | where TimeGenerated between (ago(lookback) .. starttime) + | extend FileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName) + | extend ClientUserAgent = tostring(parse_json(tostring(AdditionalProperties)).ClientUserAgent) + | extend SiteUrl = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) + | where isnotempty(ObjectId) + | where ObjectId !~ FileName + | where ObjectId in (Reserved) or FileName in (Reserved) + | where ClientUserAgent !has "Mac OS" + | summarize PrevSeenCount = count() by ObjectId, UserId, FileName + ) on ObjectId + | extend SiteUrlUserFolder = tolower(split(SiteUrl, '/')[-2]) + | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\.', '_')) + | extend UserIdDiffThanUserFolder = iff(SiteUrl has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false) + | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(ClientUserAgent, 100000), Ids = make_list(Id, 100000), + SourceRelativeUrls = make_list(ObjectId, 100000), FileNames = make_list(FileName, 100000) by Workload, RecordType, UserType, UserKey, UserId, ClientIp, SiteUrl, ObjectId, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend IP_0_Address = ClientIp + | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, URL_0_Url = SiteUrl; + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where TimeGenerated between (starttime .. endtime) + | where isnotempty(SourceFileExtension) + | where SourceFileName !~ SourceFileExtension + | where SourceFileExtension in (Reserved) or SourceFileName in (Reserved) + | where UserAgent !has "Mac OS" + | join kind=leftanti ( + OfficeActivity + | where TimeGenerated between (ago(lookback) .. starttime) + | where isnotempty(SourceFileExtension) + | where SourceFileName !~ SourceFileExtension + | where SourceFileExtension in (Reserved) or SourceFileName in (Reserved) + | where UserAgent !has "Mac OS" + | summarize PrevSeenCount = count() by SourceFileExtension + ) on SourceFileExtension + | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2]) + | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\.', '_')) + | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false) + | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(UserAgent, 100000), OfficeIds = make_list(OfficeId, 100000), + SourceRelativeUrls = make_list(SourceRelativeUrl, 100000), FileNames = make_list(SourceFileName, 100000) by OfficeWorkload, RecordType, UserType, UserKey, UserId, ClientIP, Site_Url, SourceFileExtension, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend IP_0_Address = ClientIP + | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, URL_0_Url = Site_Url; + // Combine Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(StartTime, *) by UserId, ClientIP; + // Final Output + CombinedEvents + | project StartTime, EndTime, Operations, UserAgents, IP_0_Address, Account_0_Name, Account_0_UPNSuffix, URL_0_Url + | order by StartTime desc entityMappings: - entityType: IP fieldMappings: @@ -70,5 +102,5 @@ entityMappings: fieldMappings: - identifier: Url columnName: URL_0_Url -version: 2.0.1 +version: 2.0.2 kind: Scheduled diff --git a/Solutions/Global Secure Access/Hunting Queries/OfficeMailForwarding_hunting.yaml b/Solutions/Global Secure Access/Hunting Queries/OfficeMailForwarding_hunting.yaml index 7734458918..6cad3cce32 100644 --- a/Solutions/Global Secure Access/Hunting Queries/OfficeMailForwarding_hunting.yaml +++ b/Solutions/Global Secure Access/Hunting Queries/OfficeMailForwarding_hunting.yaml @@ -1,5 +1,5 @@ id: d49fc965-aef3-49f6-89ad-10cc4697eb5b -name: Office Mail Forwarding - Hunting Version +name: GSA Enriched Office 365 - Office Mail Forwarding - Hunting Version description: | Adversaries often abuse email-forwarding rules to monitor victim activities, steal information, and gain intelligence on the victim or their organization. This query highlights cases where user mail is being forwarded, including to external domains. description-detailed: | @@ -17,51 +17,63 @@ relevantTechniques: - T1114 - T1020 query: | - EnrichedMicrosoft365AuditLogs - | where Workload == "Exchange" - | where (Operation == "Set-Mailbox" and tostring(parse_json(tostring(AdditionalProperties))) contains 'ForwardingSmtpAddress') - or (Operation in ('New-InboxRule', 'Set-InboxRule') and (tostring(parse_json(tostring(AdditionalProperties))) contains 'ForwardTo' or tostring(parse_json(tostring(AdditionalProperties))) contains 'RedirectTo')) - | extend parsed = parse_json(tostring(AdditionalProperties)) - | extend fwdingDestination_initial = iif(Operation == "Set-Mailbox", tostring(parsed.ForwardingSmtpAddress), coalesce(tostring(parsed.ForwardTo), tostring(parsed.RedirectTo))) - | where isnotempty(fwdingDestination_initial) - | extend fwdingDestination = iff(fwdingDestination_initial has "smtp", (split(fwdingDestination_initial, ":")[1]), fwdingDestination_initial) - | parse fwdingDestination with * '@' ForwardedtoDomain - | parse UserId with *'@' UserDomain - | extend subDomain = ((split(strcat(tostring(split(UserDomain, '.')[-2]), '.', tostring(split(UserDomain, '.')[-1])), '.'))[0]) - | where ForwardedtoDomain !contains subDomain - | extend Result = iff(ForwardedtoDomain != UserDomain, "Mailbox rule created to forward to External Domain", "Forward rule for Internal domain") - | extend ClientIPAddress = case(ClientIp has ".", tostring(split(ClientIp, ":")[0]), ClientIp has "[", tostring(trim_start(@'[[]', tostring(split(ClientIp, "]")[0]))), ClientIp) - | extend Port = case( - ClientIp has ".", - (split(ClientIp, ":")[1]), - ClientIp has "[", - tostring(split(ClientIp, "]:")[1]), - ClientIp - ) - | project - TimeGenerated, - UserId, - UserDomain, - subDomain, - Operation, - ForwardedtoDomain, - ClientIPAddress, - Result, - Port, - ObjectId, - fwdingDestination, - AdditionalProperties - | extend - AccountName = tostring(split(UserId, "@")[0]), - AccountUPNSuffix = tostring(split(UserId, "@")[1]) - | extend Host = tostring(parse_json(tostring(AdditionalProperties)).OriginatingServer) - | extend HostName = tostring(split(Host, ".")[0]) - | extend DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.')) - | extend Account_0_Name = AccountName - | extend Account_0_UPNSuffix = AccountUPNSuffix - | extend IP_0_Address = ClientIPAddress - | extend Host_0_HostName = HostName - | extend Host_0_DnsDomain = DnsDomain + let starttime = todatetime('{{StartTimeISO}}'); + let endtime = todatetime('{{EndTimeISO}}'); + + // Enriched Logs Query for forwarding rule operations + let EnrichedForwardRules = EnrichedMicrosoft365AuditLogs + | where TimeGenerated between (starttime .. endtime) + | where Workload == "Exchange" + | where (Operation == "Set-Mailbox" and tostring(parse_json(tostring(AdditionalProperties))) contains 'ForwardingSmtpAddress') + or (Operation in ('New-InboxRule', 'Set-InboxRule') and (tostring(parse_json(tostring(AdditionalProperties))) contains 'ForwardTo' or tostring(parse_json(tostring(AdditionalProperties))) contains 'RedirectTo')) + | extend parsed = parse_json(tostring(AdditionalProperties)) + | extend fwdingDestination_initial = iif(Operation == "Set-Mailbox", tostring(parsed.ForwardingSmtpAddress), coalesce(tostring(parsed.ForwardTo), tostring(parsed.RedirectTo))) + | where isnotempty(fwdingDestination_initial) + | extend fwdingDestination = iff(fwdingDestination_initial has "smtp", (split(fwdingDestination_initial, ":")[1]), fwdingDestination_initial) + | parse fwdingDestination with * '@' ForwardedtoDomain + | parse UserId with * '@' UserDomain + | extend subDomain = ((split(strcat(tostring(split(UserDomain, '.')[-2]), '.', tostring(split(UserDomain, '.')[-1])), '.'))[0]) + | where ForwardedtoDomain !contains subDomain + | extend Result = iff(ForwardedtoDomain != UserDomain, "Mailbox rule created to forward to External Domain", "Forward rule for Internal domain") + | extend ClientIPAddress = case(ClientIp has ".", tostring(split(ClientIp, ":")[0]), ClientIp has "[", tostring(trim_start(@'[[]', tostring(split(ClientIp, "]")[0]))), ClientIp) + | extend Port = case(ClientIp has ".", (split(ClientIp, ":")[1]), ClientIp has "[", tostring(split(ClientIp, "]:")[1]), ClientIp) + | extend Host = tostring(parse_json(tostring(AdditionalProperties)).OriginatingServer) + | extend HostName = tostring(split(Host, ".")[0]) + | extend DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.')) + | project TimeGenerated, UserId, UserDomain, subDomain, Operation, ForwardedtoDomain, ClientIPAddress, Result, Port, ObjectId, fwdingDestination, HostName, DnsDomain + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend IP_0_Address = ClientIPAddress, Host_0_HostName = HostName, Host_0_DnsDomain = DnsDomain, Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix; + // Office Activity Query for forwarding rule operations + let OfficeForwardRules = OfficeActivity + | where TimeGenerated between (starttime .. endtime) + | where OfficeWorkload == "Exchange" + | where (Operation =~ "Set-Mailbox" and Parameters contains 'ForwardingSmtpAddress') + or (Operation in~ ('New-InboxRule', 'Set-InboxRule') and (Parameters contains 'ForwardTo' or Parameters contains 'RedirectTo')) + | extend parsed = parse_json(Parameters) + | extend fwdingDestination_initial = (iif(Operation =~ "Set-Mailbox", tostring(parsed[1].Value), tostring(parsed[2].Value))) + | where isnotempty(fwdingDestination_initial) + | extend fwdingDestination = iff(fwdingDestination_initial has "smtp", (split(fwdingDestination_initial, ":")[1]), fwdingDestination_initial) + | parse fwdingDestination with * '@' ForwardedtoDomain + | parse UserId with * '@' UserDomain + | extend subDomain = ((split(strcat(tostring(split(UserDomain, '.')[-2]), '.', tostring(split(UserDomain, '.')[-1])), '.') [0])) + | where ForwardedtoDomain !contains subDomain + | extend Result = iff(ForwardedtoDomain != UserDomain, "Mailbox rule created to forward to External Domain", "Forward rule for Internal domain") + | extend ClientIPAddress = case(ClientIP has ".", tostring(split(ClientIP, ":")[0]), ClientIP has "[", tostring(trim_start(@'[[]', tostring(split(ClientIP, "]")[0]))), ClientIP) + | extend Port = case(ClientIP has ".", (split(ClientIP, ":")[1]), ClientIP has "[", tostring(split(ClientIP, "]:")[1]), ClientIP) + | extend Host = tostring(split(OriginatingServer, " (")[0]) + | extend HostName = tostring(split(Host, ".")[0]) + | extend DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.')) + | project TimeGenerated, UserId, UserDomain, subDomain, Operation, ForwardedtoDomain, ClientIPAddress, Result, Port, HostName, DnsDomain + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend IP_0_Address = ClientIPAddress, Host_0_HostName = HostName, Host_0_DnsDomain = DnsDomain, Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix; + // Combine the results from both Enriched and Office Activity logs + let CombinedForwardRules = EnrichedForwardRules + | union OfficeForwardRules + | summarize arg_min(TimeGenerated, *) by UserId, ForwardedtoDomain, Operation + | project TimeGenerated, UserId, UserDomain, subDomain, Operation, ForwardedtoDomain, ClientIPAddress, Result, Port, ObjectId, fwdingDestination, Host_0_HostName, Host_0_DnsDomain, IP_0_Address, Account_0_Name, Account_0_UPNSuffix; + // Final output + CombinedForwardRules + | order by TimeGenerated desc; entityMappings: - entityType: Account fieldMappings: @@ -79,5 +91,5 @@ entityMappings: columnName: Host_0_HostName - identifier: DnsDomain columnName: Host_0_DnsDomain -version: 2.0.1 +version: 2.0.2 kind: Scheduled diff --git a/Solutions/Global Secure Access/Hunting Queries/TeamsFilesUploaded.yaml b/Solutions/Global Secure Access/Hunting Queries/TeamsFilesUploaded.yaml index a61d6f2fa9..164e850522 100644 --- a/Solutions/Global Secure Access/Hunting Queries/TeamsFilesUploaded.yaml +++ b/Solutions/Global Secure Access/Hunting Queries/TeamsFilesUploaded.yaml @@ -1,5 +1,5 @@ id: 90e198a9-efb6-4719-ad89-81b8e93633a7 -name: Files uploaded to teams and access summary +name: GSA Enriched Office 365 - Files uploaded to teams and access summary description: | 'This hunting query identifies files uploaded to SharePoint via a Teams chat and summarizes users and IP addresses that have accessed these files. This allows for @@ -16,27 +16,52 @@ relevantTechniques: - T1102 - T1078 query: | - EnrichedMicrosoft365AuditLogs - | where RecordType == "SharePointFileOperation" - | where Operation == "FileUploaded" - | where UserId != "app@sharepoint" - | where ObjectId has "Microsoft Teams Chat Files" - | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName) - | join kind=leftouter ( - EnrichedMicrosoft365AuditLogs - | where RecordType == "SharePointFileOperation" - | where Operation == "FileDownloaded" or Operation == "FileAccessed" - | where UserId != "app@sharepoint" - | where ObjectId has "Microsoft Teams Chat Files" - | extend UserId1 = UserId, ClientIp1 = ClientIp - ) on ObjectId - | extend userBag = bag_pack("UserId1", UserId1, "ClientIp1", ClientIp1) - | summarize AccessedBy = make_bag(userBag), make_set(UserId1, 10000) by bin(TimeGenerated, 1h), UserId, ObjectId, SourceFileName - | extend NumberOfUsersAccessed = array_length(bag_keys(AccessedBy)) - | project timestamp = TimeGenerated, UserId, FileLocation = ObjectId, FileName = SourceFileName, AccessedBy, NumberOfUsersAccessed - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) - | extend Account_0_Name = AccountName - | extend Account_0_UPNSuffix = AccountUPNSuffix + // Define the query for EnrichedMicrosoft365AuditLogs + let enrichedLogs = EnrichedMicrosoft365AuditLogs + | where RecordType == "SharePointFileOperation" + | where Operation == "FileUploaded" + | where UserId != "app@sharepoint" + | where ObjectId has "Microsoft Teams Chat Files" + | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName) + | join kind=leftouter ( + EnrichedMicrosoft365AuditLogs + | where RecordType == "SharePointFileOperation" + | where Operation in ("FileDownloaded", "FileAccessed") + | where UserId != "app@sharepoint" + | where ObjectId has "Microsoft Teams Chat Files" + | extend UserId1 = UserId, ClientIp1 = ClientIp + ) on ObjectId + | extend userBag = bag_pack("UserId1", UserId1, "ClientIp1", ClientIp1) + | summarize AccessedBy = make_bag(userBag), make_set(UserId1, 10000) by bin(TimeGenerated, 1h), UserId, ObjectId, SourceFileName + | extend NumberOfUsersAccessed = array_length(bag_keys(AccessedBy)) + | project timestamp = TimeGenerated, UserId, FileLocation = ObjectId, FileName = SourceFileName, AccessedBy, NumberOfUsersAccessed + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend Account_0_Name = AccountName + | extend Account_0_UPNSuffix = AccountUPNSuffix; + // Define the query for OfficeActivity + let officeLogs = OfficeActivity + | where RecordType == "SharePointFileOperation" + | where Operation == "FileUploaded" + | where UserId != "app@sharepoint" + | where SourceRelativeUrl has "Microsoft Teams Chat Files" + | join kind=leftouter ( + OfficeActivity + | where RecordType == "SharePointFileOperation" + | where Operation in ("FileDownloaded", "FileAccessed") + | where UserId != "app@sharepoint" + | where SourceRelativeUrl has "Microsoft Teams Chat Files" + ) on OfficeObjectId + | extend userBag = bag_pack(UserId1, ClientIP1) + | summarize AccessedBy = make_bag(userBag, 10000), make_set(UserId1, 10000) by TimeGenerated, UserId, OfficeObjectId, SourceFileName + | extend NumberUsers = array_length(bag_keys(AccessedBy)) + | project timestamp = TimeGenerated, UserId, FileLocation = OfficeObjectId, FileName = SourceFileName, AccessedBy, NumberOfUsersAccessed = NumberUsers + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend Account_0_Name = AccountName + | extend Account_0_UPNSuffix = AccountUPNSuffix; + // Union both results + enrichedLogs + | union officeLogs + | order by timestamp desc; entityMappings: - entityType: Account fieldMappings: @@ -44,4 +69,4 @@ entityMappings: columnName: AccountName - identifier: UPNSuffix columnName: AccountUPNSuffix -version: 2.0.1 +version: 2.0.2 diff --git a/Solutions/Global Secure Access/Hunting Queries/UserAddToTeamsAndUploadsFile.yaml b/Solutions/Global Secure Access/Hunting Queries/UserAddToTeamsAndUploadsFile.yaml index f1ba1da23c..e8dde43e14 100644 --- a/Solutions/Global Secure Access/Hunting Queries/UserAddToTeamsAndUploadsFile.yaml +++ b/Solutions/Global Secure Access/Hunting Queries/UserAddToTeamsAndUploadsFile.yaml @@ -1,5 +1,5 @@ id: 3d6d0c04-7337-40cf-ace6-c471d442356d -name: User added to Teams and immediately uploads file +name: GSA Enriched Office 365 - User added to Teams and immediately uploads file description: | 'This hunting query identifies users who are added to a Teams Channel or Teams chat and within 1 minute of being added upload a file via the chat. This might be @@ -13,25 +13,50 @@ tactics: relevantTechniques: - T1566 query: | - let threshold = 1m; - let MemberAddedEvents = EnrichedMicrosoft365AuditLogs - | where Workload == "MicrosoftTeams" - | where Operation == "MemberAdded" - | extend TeamName = tostring(parse_json(AdditionalProperties).TeamName) - | project TimeGenerated, UploaderID = UserId, TeamName; - let FileUploadEvents = EnrichedMicrosoft365AuditLogs - | where RecordType == "SharePointFileOperation" - | where ObjectId has "Microsoft Teams Chat Files" - | where Operation == "FileUploaded" - | extend SourceFileName = tostring(parse_json(AdditionalProperties).SourceFileName) - | project UploadTime = TimeGenerated, UploaderID = UserId, FileLocation = ObjectId, SourceFileName; - MemberAddedEvents - | join kind=inner (FileUploadEvents) on UploaderID - | where UploadTime > TimeGenerated and UploadTime < TimeGenerated + threshold - | extend timestamp = TimeGenerated, AccountCustomEntity = UploaderID + let threshold = 1m; + // Define MemberAddedEvents for EnrichedMicrosoft365AuditLogs + let MemberAddedEvents_Enriched = EnrichedMicrosoft365AuditLogs + | where Workload == "MicrosoftTeams" + | where Operation == "MemberAdded" + | extend TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName) + | project TimeGenerated, UploaderID = UserId, TeamName; + // Define FileUploadEvents for EnrichedMicrosoft365AuditLogs + let FileUploadEvents_Enriched = EnrichedMicrosoft365AuditLogs + | where RecordType == "SharePointFileOperation" + | where ObjectId has "Microsoft Teams Chat Files" + | where Operation == "FileUploaded" + | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName) + | project UploadTime = TimeGenerated, UploaderID = UserId, FileLocation = ObjectId, SourceFileName; + // Perform join for EnrichedMicrosoft365AuditLogs + let EnrichedResults = MemberAddedEvents_Enriched + | join kind=inner (FileUploadEvents_Enriched) on UploaderID + | where UploadTime > TimeGenerated and UploadTime < TimeGenerated + threshold + | extend timestamp = TimeGenerated, AccountCustomEntity = UploaderID; + // Define MemberAddedEvents for OfficeActivity + let MemberAddedEvents_Office = OfficeActivity + | where OfficeWorkload == "MicrosoftTeams" + | where Operation == "MemberAdded" + | extend TeamName = iff(isempty(TeamName), Members[0].UPN, TeamName) + | project TimeGenerated, UploaderID = UserId, TeamName; + // Define FileUploadEvents for OfficeActivity + let FileUploadEvents_Office = OfficeActivity + | where RecordType == "SharePointFileOperation" + | where SourceRelativeUrl has "Microsoft Teams Chat Files" + | where Operation == "FileUploaded" + | project UploadTime = TimeGenerated, UploaderID = UserId, FileLocation = OfficeObjectId, FileName = SourceFileName; + // Perform join for OfficeActivity + let OfficeResults = MemberAddedEvents_Office + | join kind=inner (FileUploadEvents_Office) on UploaderID + | where UploadTime > TimeGenerated and UploadTime < TimeGenerated + threshold + | project-away UploaderID1 + | extend timestamp = TimeGenerated, AccountCustomEntity = UploaderID; + // Union both results + EnrichedResults + | union OfficeResults + | order by timestamp desc; entityMappings: - entityType: Account fieldMappings: - identifier: Name columnName: AccountCustomEntity -version: 2.0.1 +version: 2.0.2 diff --git a/Solutions/Global Secure Access/Hunting Queries/WindowsReservedFileNamesOnOfficeFileServices.yaml b/Solutions/Global Secure Access/Hunting Queries/WindowsReservedFileNamesOnOfficeFileServices.yaml index 226886457a..35997c9b51 100644 --- a/Solutions/Global Secure Access/Hunting Queries/WindowsReservedFileNamesOnOfficeFileServices.yaml +++ b/Solutions/Global Secure Access/Hunting Queries/WindowsReservedFileNamesOnOfficeFileServices.yaml @@ -1,5 +1,5 @@ id: 61c28cd7-3139-4731-8ea7-2cbbeabb4684 -name: Windows Reserved Filenames Staged on Office File Services +name: GSA Enriched Office 365 - Windows Reserved Filenames Staged on Office File Services description: | 'This identifies Windows Reserved Filenames on Office services like SharePoint and OneDrive. It also detects when a user uploads these files to another user's workspace, which may indicate malicious activity.' description-detailed: | @@ -18,27 +18,44 @@ tactics: relevantTechniques: - T1105 query: | - // Reserved FileNames/Extension for Windows - let Reserved = dynamic(['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']); - EnrichedMicrosoft365AuditLogs - | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName) - | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) - | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) - | where isnotempty(ObjectId) - | where ObjectId in (Reserved) or SourceFileName in (Reserved) - | where UserAgent !has "Mac OS" - | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2]) - | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\.', '_')) - // identify when UserId is not a match to the specific site url personal folder reference - | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false) - | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(UserAgent, 100000), ObjectIds = make_list(Id, 100000), SourceRelativeUrls = make_list(ObjectId, 100000), FileNames = make_list(SourceFileName, 100000) - by Workload, RecordType, UserType, UserKey, UserId, ClientIp, Site_Url, ObjectId, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder - // Use mvexpand on any list items and you can expand out the exact time and other metadata about the hit - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) - | extend IP_0_Address = ClientIp - | extend Account_0_Name = AccountName - | extend Account_0_UPNSuffix = AccountUPNSuffix - | extend URL_0_Url = Site_Url + let Reserved = dynamic(['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']); + // Query for OfficeActivity + let OfficeActivityResults = OfficeActivity + | where isnotempty(SourceFileExtension) + | where SourceFileExtension in (Reserved) or SourceFileName in (Reserved) + | where UserAgent !has "Mac OS" + | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2]) + | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\.', '_')) + | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false) + | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(UserAgent, 100000), OfficeIds = make_list(OfficeId, 100000), SourceRelativeUrls = make_list(SourceRelativeUrl, 100000), FileNames = make_list(SourceFileName, 100000) + by OfficeWorkload, RecordType, UserType, UserKey, UserId, ClientIP, Site_Url, SourceFileExtension, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend IP_0_Address = ClientIP + | extend Account_0_Name = AccountName + | extend Account_0_UPNSuffix = AccountUPNSuffix + | extend URL_0_Url = Site_Url; + // Query for EnrichedMicrosoft365AuditLogs + let EnrichedMicrosoft365Results = EnrichedMicrosoft365AuditLogs + | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName) + | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) + | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) + | where isnotempty(ObjectId) + | where ObjectId in (Reserved) or SourceFileName in (Reserved) + | where UserAgent !has "Mac OS" + | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2]) + | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\.', '_')) + | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false) + | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(UserAgent, 100000), ObjectIds = make_list(Id, 100000), SourceRelativeUrls = make_list(ObjectId, 100000), FileNames = make_list(SourceFileName, 100000) + by Workload, RecordType, UserType, UserKey, UserId, ClientIp, Site_Url, ObjectId, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend IP_0_Address = ClientIp + | extend Account_0_Name = AccountName + | extend Account_0_UPNSuffix = AccountUPNSuffix + | extend URL_0_Url = Site_Url; + // Combine both queries + OfficeActivityResults + | union EnrichedMicrosoft365Results + | order by StartTime desc; entityMappings: - entityType: IP fieldMappings: @@ -54,4 +71,4 @@ entityMappings: fieldMappings: - identifier: Url columnName: Site_Url -version: 2.0.1 +version: 2.0.2 diff --git a/Solutions/Global Secure Access/Hunting Queries/new_sharepoint_downloads_by_IP.yaml b/Solutions/Global Secure Access/Hunting Queries/new_sharepoint_downloads_by_IP.yaml index 1ef34600f9..271f7cb1e0 100644 --- a/Solutions/Global Secure Access/Hunting Queries/new_sharepoint_downloads_by_IP.yaml +++ b/Solutions/Global Secure Access/Hunting Queries/new_sharepoint_downloads_by_IP.yaml @@ -1,5 +1,5 @@ id: e3d24cfd-b2a1-4ba7-8f80-0360892f9d57 -name: SharePointFileOperation via previously unseen IPs +name: GSA Enriched Office 365 - SharePointFileOperation via previously unseen IPs description: | 'Shows SharePoint upload/download volume by IPs with high-risk ASNs. New IPs with volume spikes may be unauthorized and exfiltrating documents.' description-detailed: | @@ -22,29 +22,46 @@ query: | let endtime = todatetime('{{EndTimeISO}}'); let lookback = starttime - 14d; let BLOCK_THRESHOLD = 1.0; - let HighBlockRateASNs = - SigninLogs - | where TimeGenerated > lookback - | where isnotempty(AutonomousSystemNumber) - | summarize make_set(IPAddress), TotalIps = dcount(IPAddress), BlockedSignins = countif(ResultType == "50053"), TotalSignins = count() by AutonomousSystemNumber - | extend BlockRatio = 1.00 * BlockedSignins / TotalSignins - | where BlockRatio >= BLOCK_THRESHOLD - | distinct AutonomousSystemNumber; - let ASNIPs = - SigninLogs - | where TimeGenerated > lookback - | where AutonomousSystemNumber in (HighBlockRateASNs) - | distinct IPAddress, AutonomousSystemNumber; - EnrichedMicrosoft365AuditLogs - | where TimeGenerated between (starttime .. endtime) - | where RecordType == "SharePointFileOperation" - | where Operation in ("FileDownloaded", "FileUploaded") - | where ClientIp in (ASNIPs) - | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by ClientIp - | extend IP_0_Address = ClientIp + // Identify Autonomous System Numbers (ASNs) with a high block rate in Sign-in Logs + let HighBlockRateASNs = SigninLogs + | where TimeGenerated > lookback + | where isnotempty(AutonomousSystemNumber) + | summarize make_set(IPAddress), TotalIps = dcount(IPAddress), BlockedSignins = countif(ResultType == "50053"), TotalSignins = count() by AutonomousSystemNumber + | extend BlockRatio = 1.00 * BlockedSignins / TotalSignins + | where BlockRatio >= BLOCK_THRESHOLD + | distinct AutonomousSystemNumber; + // Retrieve IP addresses from these high block rate ASNs + let ASNIPs = SigninLogs + | where TimeGenerated > lookback + | where AutonomousSystemNumber in (HighBlockRateASNs) + | distinct IPAddress, AutonomousSystemNumber; + // OfficeActivity Query: File activities from identified ASN IPs + let OfficeEvents = OfficeActivity + | where TimeGenerated between(starttime .. endtime) + | where RecordType == "SharePointFileOperation" + | where Operation in ("FileDownloaded", "FileUploaded") + | where ClientIP in (ASNIPs) + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by ClientIP + | extend IP_0_Address = ClientIP; + // EnrichedMicrosoft365AuditLogs Query: File activities from identified ASN IPs + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where TimeGenerated between (starttime .. endtime) + | where RecordType == "SharePointFileOperation" + | where Operation in ("FileDownloaded", "FileUploaded") + | where ClientIp in (ASNIPs) + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by ClientIp + | extend IP_0_Address = ClientIp; + // Combine Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(StartTime, *) by ClientIP; + // Final Output + CombinedEvents + | project StartTime, EndTime, RecentFileActivities, IP_0_Address + | order by StartTime desc entityMappings: - entityType: IP fieldMappings: - identifier: Address columnName: IP_0_Address -version: 2.0.1 +version: 2.0.2 diff --git a/Solutions/Global Secure Access/Hunting Queries/new_sharepoint_downloads_by_UserAgent.yaml b/Solutions/Global Secure Access/Hunting Queries/new_sharepoint_downloads_by_UserAgent.yaml index be10886060..fe4a679dbc 100644 --- a/Solutions/Global Secure Access/Hunting Queries/new_sharepoint_downloads_by_UserAgent.yaml +++ b/Solutions/Global Secure Access/Hunting Queries/new_sharepoint_downloads_by_UserAgent.yaml @@ -1,5 +1,5 @@ id: f2367171-1514-4c67-88ef-27434b6a1093 -name: SharePointFileOperation via devices with previously unseen user agents +name: GSA Enriched Office 365 -SharePointFileOperation via devices with previously unseen user agents description: | 'Tracking via user agent is one way to differentiate between types of connecting device. In homogeneous enterprise environments the user agent associated with an attacker device may stand out as unusual.' @@ -15,33 +15,47 @@ tactics: relevantTechniques: - T1030 query: | - let starttime = todatetime('{{StartTimeISO}}'); - let endtime = todatetime('{{EndTimeISO}}'); - let lookback = starttime - 14d; - let MINIMUM_BLOCKS = 10; - let SUCCESS_THRESHOLD = 0.2; - let HistoricalActivity = - SigninLogs - | where TimeGenerated > lookback - | where isnotempty(ClientAppUsed) - | summarize SuccessfulSignins = countif(ResultType == "0"), BlockedSignins = countif(ResultType == "50053") by ClientAppUsed - | extend SuccessBlockRatio = 1.00 * SuccessfulSignins / BlockedSignins - | where SuccessBlockRatio < SUCCESS_THRESHOLD - | where BlockedSignins > MINIMUM_BLOCKS; - EnrichedMicrosoft365AuditLogs - | where TimeGenerated between (starttime .. endtime) - | where RecordType == "SharePointFileOperation" - | where Operation in ("FileDownloaded", "FileUploaded") - | extend ClientAppUsed = tostring(parse_json(AdditionalProperties).UserAgent) - | extend SiteUrl = tostring(parse_json(AdditionalProperties).SiteUrl) - | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by ClientAppUsed, UserId, ClientIp, SiteUrl - | join kind=innerunique (HistoricalActivity) on ClientAppUsed - | project-away ClientAppUsed1 - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) - | extend IP_0_Address = ClientIp - | extend Account_0_Name = AccountName - | extend Account_0_UPNSuffix = AccountUPNSuffix - | extend URL_0_Url = SiteUrl + let starttime = todatetime('{{StartTimeISO}}'); + let endtime = todatetime('{{EndTimeISO}}'); + let lookback = starttime - 14d; + let MINIMUM_BLOCKS = 10; + let SUCCESS_THRESHOLD = 0.2; + // Identify user agents or client apps with a low success-to-block ratio in Sign-in Logs + let HistoricalActivity = SigninLogs + | where TimeGenerated > lookback + | where isnotempty(UserAgent) + | summarize SuccessfulSignins = countif(ResultType == "0"), BlockedSignins = countif(ResultType == "50053") by UserAgent + | extend SuccessBlockRatio = 1.00 * SuccessfulSignins / BlockedSignins + | where SuccessBlockRatio < SUCCESS_THRESHOLD + | where BlockedSignins > MINIMUM_BLOCKS; + // OfficeActivity Query: File operations by matching user agents + let OfficeEvents = OfficeActivity + | where TimeGenerated between (starttime .. endtime) + | where RecordType == "SharePointFileOperation" + | where Operation in ("FileDownloaded", "FileUploaded") + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by UserAgent, UserId, ClientIP, Site_Url + | join kind=innerunique (HistoricalActivity) on UserAgent + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend IP_0_Address = ClientIP, Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, URL_0_Url = Site_Url; + // EnrichedMicrosoft365AuditLogs Query: File operations by matching client apps (UserAgent) + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where TimeGenerated between (starttime .. endtime) + | where RecordType == "SharePointFileOperation" + | where Operation in ("FileDownloaded", "FileUploaded") + | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) // Ensure matching with UserAgent column + | extend SiteUrl = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl) + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by UserAgent, UserId, ClientIp, SiteUrl + | join kind=innerunique (HistoricalActivity) on UserAgent + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend IP_0_Address = ClientIp, Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, URL_0_Url = SiteUrl; + // Combine Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(StartTime, *) by UserId, ClientIP; + // Final Output + CombinedEvents + | project StartTime, EndTime, RecentFileActivities, IP_0_Address, Account_0_Name, Account_0_UPNSuffix, URL_0_Url + | order by StartTime desc; entityMappings: - entityType: IP fieldMappings: @@ -57,5 +71,5 @@ entityMappings: fieldMappings: - identifier: Url columnName: URL_0_Url -version: 2.0.1 +version: 2.0.2 kind: Scheduled diff --git a/Solutions/Global Secure Access/Hunting Queries/nonowner_MailboxLogin.yaml b/Solutions/Global Secure Access/Hunting Queries/nonowner_MailboxLogin.yaml index 43497e3531..a8397ba778 100644 --- a/Solutions/Global Secure Access/Hunting Queries/nonowner_MailboxLogin.yaml +++ b/Solutions/Global Secure Access/Hunting Queries/nonowner_MailboxLogin.yaml @@ -1,5 +1,5 @@ id: 0a8f410d-38b5-4d75-90da-32b472b97230 -name: Non-owner mailbox login activity +name: GSA Enriched Office 365 - Non-owner mailbox login activity description: | 'Finds non-owner mailbox access by admin/delegate permissions. Whitelist valid users and check others for unauthorized access.' description-detailed: | @@ -22,17 +22,37 @@ tags: - Solorigate - NOBELIUM query: | - EnrichedMicrosoft365AuditLogs - | where Workload == "Exchange" - | where Operation == "MailboxLogin" - | extend Logon_Type = tostring(parse_json(tostring(AdditionalProperties)).LogonType) - | extend MailboxOwnerUPN = tostring(parse_json(tostring(AdditionalProperties)).MailboxOwnerUPN) - | where Logon_Type != "Owner" - | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by Operation, UserType, UserId, MailboxOwnerUPN, Logon_Type, ClientIp - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) - | extend IP_0_Address = ClientIp - | extend Account_0_Name = AccountName - | extend Account_0_UPNSuffix = AccountUPNSuffix + let starttime = todatetime('{{StartTimeISO}}'); + let endtime = todatetime('{{EndTimeISO}}'); + // Enriched Logs Query for Mailbox Logins (non-owner) + let EnrichedMailboxLogins = EnrichedMicrosoft365AuditLogs + | where TimeGenerated between (starttime .. endtime) + | where Workload == "Exchange" + | where Operation == "MailboxLogin" + | extend Logon_Type = tostring(parse_json(tostring(AdditionalProperties)).LogonType) + | extend MailboxOwnerUPN = tostring(parse_json(tostring(AdditionalProperties)).MailboxOwnerUPN) + | where Logon_Type != "Owner" + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by Operation, UserType, UserId, MailboxOwnerUPN, Logon_Type, ClientIp + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend IP_0_Address = ClientIp + | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix; + // Office Activity Query for Mailbox Logins (non-owner) + let OfficeMailboxLogins = OfficeActivity + | where TimeGenerated between (starttime .. endtime) + | where OfficeWorkload == "Exchange" + | where Operation == "MailboxLogin" and Logon_Type != "Owner" + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by Operation, OrganizationName, UserType, UserId, MailboxOwnerUPN, Logon_Type, ClientIP + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]) + | extend IP_0_Address = ClientIP + | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix; + // Combine both results + let CombinedMailboxLogins = EnrichedMailboxLogins + | union OfficeMailboxLogins + | summarize arg_min(StartTime, *) by UserId, MailboxOwnerUPN, Logon_Type; + // Final output + CombinedMailboxLogins + | project StartTime, EndTime, Operation, UserId, MailboxOwnerUPN, Logon_Type, Account_0_Name, Account_0_UPNSuffix, IP_0_Address + | order by StartTime desc entityMappings: - entityType: Account fieldMappings: diff --git a/Solutions/Global Secure Access/Hunting Queries/powershell_or_nonbrowser_MailboxLogin.yaml b/Solutions/Global Secure Access/Hunting Queries/powershell_or_nonbrowser_MailboxLogin.yaml index a04c13e8d0..298d6ca30d 100644 --- a/Solutions/Global Secure Access/Hunting Queries/powershell_or_nonbrowser_MailboxLogin.yaml +++ b/Solutions/Global Secure Access/Hunting Queries/powershell_or_nonbrowser_MailboxLogin.yaml @@ -1,5 +1,5 @@ id: 49a4f65a-fe18-408e-afec-042fde93d3ce -name: PowerShell or non-browser mailbox login activity +name: GSA Enriched Office 365 - PowerShell or non-browser mailbox login activity description: | 'Detects mailbox login from Exchange PowerShell. All accounts can use it by default, but admins can change it. Whitelist benign activities.' description-detailed: | @@ -21,18 +21,41 @@ relevantTechniques: - T1098 - T1114 query: | - EnrichedMicrosoft365AuditLogs - | where Workload == "Exchange" and Operation == "MailboxLogin" - | extend ClientApplication = tostring(parse_json(AdditionalProperties).ClientInfoString) - | where ClientApplication == "Client=Microsoft.Exchange.Powershell; Microsoft WinRM Client" - | extend TenantName = tostring(parse_json(AdditionalProperties).TenantName) - | extend MailboxOwner = tostring(parse_json(AdditionalProperties).MailboxOwnerUPN) - | extend LogonType = tostring(parse_json(AdditionalProperties).LogonType) - | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by Operation, TenantName, UserType, UserId, MailboxOwner, LogonType, ClientApplication - | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), UserId) - | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '') - | extend AccountName = iff(UserId contains '\\', tostring(split(UserId, '\\')[1]), AccountName) - | extend AccountNTDomain = iff(UserId contains '\\', tostring(split(UserId, '\\')[0]), '') + let starttime = todatetime('{{StartTimeISO}}'); + let endtime = todatetime('{{EndTimeISO}}'); + // EnrichedMicrosoft365AuditLogs query + let EnrichedMailboxLogin = EnrichedMicrosoft365AuditLogs + | where TimeGenerated between (starttime .. endtime) + | where Workload == "Exchange" and Operation == "MailboxLogin" + | extend ClientApplication = tostring(parse_json(AdditionalProperties).ClientInfoString) + | where ClientApplication == "Client=Microsoft.Exchange.Powershell; Microsoft WinRM Client" + | extend TenantName = tostring(parse_json(AdditionalProperties).TenantName) + | extend MailboxOwner = tostring(parse_json(AdditionalProperties).MailboxOwnerUPN) + | extend LogonType = tostring(parse_json(AdditionalProperties).LogonType) + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Count = count() by Operation, TenantName, UserType, UserId, MailboxOwner, LogonType, ClientApplication + | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), UserId) + | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '') + | extend AccountName = iff(UserId contains '\\', tostring(split(UserId, '\\')[1]), AccountName) + | extend AccountNTDomain = iff(UserId contains '\\', tostring(split(UserId, '\\')[0]), ''); + // OfficeActivity query + let OfficeMailboxLogin = OfficeActivity + | where TimeGenerated between (starttime .. endtime) + | where OfficeWorkload == "Exchange" and Operation == "MailboxLogin" + | where ClientInfoString == "Client=Microsoft.Exchange.Powershell; Microsoft WinRM Client" + | extend LogonType = "Unknown" // If LogonType does not exist, create a placeholder + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Count = count() by Operation, OrganizationName, UserType, UserId, MailboxOwnerUPN, LogonType, ClientInfoString + | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), UserId) + | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '') + | extend AccountName = iff(UserId contains '\\', tostring(split(UserId, '\\')[1]), AccountName) + | extend AccountNTDomain = iff(UserId contains '\\', tostring(split(UserId, '\\')[0]), ''); + // Combine Enriched and Office queries + let CombinedMailboxLogin = EnrichedMailboxLogin + | union OfficeMailboxLogin + | summarize arg_min(StartTime, *) by UserId, Operation + | project StartTime, EndTime, Operation, TenantName, OrganizationName, UserType, UserId, MailboxOwner, LogonType, ClientApplication, ClientInfoString, Count, AccountName, AccountUPNSuffix, AccountNTDomain; + // Final output + CombinedMailboxLogin + | order by StartTime desc; entityMappings: - entityType: Account fieldMappings: @@ -42,5 +65,5 @@ entityMappings: columnName: AccountUPNSuffix - identifier: NTDomain columnName: AccountNTDomain -version: 2.0.1 +version: 2.0.2 kind: Scheduled diff --git a/Solutions/Global Secure Access/Hunting Queries/sharepoint_downloads.yaml b/Solutions/Global Secure Access/Hunting Queries/sharepoint_downloads.yaml index 0e9fc5b856..d124fc3abe 100644 --- a/Solutions/Global Secure Access/Hunting Queries/sharepoint_downloads.yaml +++ b/Solutions/Global Secure Access/Hunting Queries/sharepoint_downloads.yaml @@ -1,5 +1,5 @@ id: e8ae1375-4640-430c-ae8e-2514d09c71eb -name: SharePoint File Operation via Client IP with Previously Unseen User Agents +name: GSA Enriched Office 365 - SharePoint File Operation via Client IP with Previously Unseen User Agents description: | New user agents associated with a client IP for SharePoint file uploads/downloads. requiredDataConnectors: @@ -11,30 +11,52 @@ tactics: relevantTechniques: - T1030 query: | - let starttime = todatetime('{{StartTimeISO}}'); - let endtime = todatetime('{{EndTimeISO}}'); - let lookback = starttime - 14d; - let historicalUA = EnrichedMicrosoft365AuditLogs - | where RecordType == "SharePointFileOperation" - | where Operation in ("FileDownloaded", "FileUploaded") - | where TimeGenerated between(lookback..starttime) - | extend ClientApplication = tostring(parse_json(AdditionalProperties).UserAgent) - | summarize by ClientIp, ClientApplication; - let recentUA = EnrichedMicrosoft365AuditLogs - | where RecordType == "SharePointFileOperation" - | where Operation in ("FileDownloaded", "FileUploaded") - | where TimeGenerated between(starttime..endtime) - | extend ClientApplication = tostring(parse_json(AdditionalProperties).UserAgent) - | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by ClientIp, ClientApplication; - recentUA | join kind=leftanti ( - historicalUA - ) on ClientIp, ClientApplication - // Some EnrichedMicrosoft365AuditLogs records do not contain ClientIp information - exclude these for fewer results - | where not(isempty(ClientIp)) - | extend IP_0_Address = ClientIp + let starttime = todatetime('{{StartTimeISO}}'); + let endtime = todatetime('{{EndTimeISO}}'); + let lookback = starttime - 14d; + // Historical user agents in EnrichedMicrosoft365AuditLogs + let historicalUA_Enriched = EnrichedMicrosoft365AuditLogs + | where RecordType == "SharePointFileOperation" + | where Operation in ("FileDownloaded", "FileUploaded") + | where TimeGenerated between (lookback .. starttime) + | extend ClientApplication = tostring(parse_json(AdditionalProperties).UserAgent) + | summarize by ClientIp, ClientApplication; + // Recent user agents in EnrichedMicrosoft365AuditLogs + let recentUA_Enriched = EnrichedMicrosoft365AuditLogs + | where RecordType == "SharePointFileOperation" + | where Operation in ("FileDownloaded", "FileUploaded") + | where TimeGenerated between (starttime .. endtime) + | extend ClientApplication = tostring(parse_json(AdditionalProperties).UserAgent) + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by ClientIp, ClientApplication; + // Combine historical and recent user agents from EnrichedMicrosoft365AuditLogs + let Enriched_UA = recentUA_Enriched + | join kind=leftanti (historicalUA_Enriched) on ClientIp, ClientApplication + | where not(isempty(ClientIp)) + | extend IP_0_Address = ClientIp; + // Historical user agents in OfficeActivity + let historicalUA_Office = OfficeActivity + | where RecordType == "SharePointFileOperation" + | where Operation in ("FileDownloaded", "FileUploaded") + | where TimeGenerated between (lookback .. starttime) + | summarize by ClientIP, UserAgent; + // Recent user agents in OfficeActivity + let recentUA_Office = OfficeActivity + | where RecordType == "SharePointFileOperation" + | where Operation in ("FileDownloaded", "FileUploaded") + | where TimeGenerated between (starttime .. endtime) + | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by ClientIP, UserAgent; + // Combine historical and recent user agents from OfficeActivity + let Office_UA = recentUA_Office + | join kind=leftanti (historicalUA_Office) on ClientIP, UserAgent + | where not(isempty(ClientIP)) + | extend IP_0_Address = ClientIP; + // Final combined result + Enriched_UA + | union Office_UA + | project StartTime, EndTime, ClientIp, ClientApplication, IP_0_Address; entityMappings: - entityType: IP fieldMappings: - identifier: Address columnName: IP_0_Address -version: 2.0.1 +version: 2.0.2 From d8ae992f7f2235b668c717ab5a7795827f1e3738 Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Wed, 2 Oct 2024 13:56:34 +0300 Subject: [PATCH 12/27] Update Office 365 - sharepoint_file_transfer_above_threshold.yaml --- .../Office 365 - sharepoint_file_transfer_above_threshold.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml index 43db365da9..f3258d6690 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml @@ -24,6 +24,7 @@ query: | let EnrichedEvents = EnrichedMicrosoft365AuditLogs | where Workload has_any("SharePoint", "OneDrive") | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | extend ClientIP = ClientIp | summarize count_distinct_ObjectId = dcount(ObjectId), fileslist = make_set(ObjectId, 10000) by UserId, ClientIP, bin(TimeGenerated, 15m) | where count_distinct_ObjectId >= threshold @@ -51,7 +52,7 @@ query: | // Final Output CombinedEvents | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist - | order by TimeGenerated desc + | order by TimeGenerated descc entityMappings: - entityType: Account fieldMappings: From f89d57e0276f1f563eee151a418ccecb37387106 Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Wed, 2 Oct 2024 14:22:51 +0300 Subject: [PATCH 13/27] Update Office 365 - sharepoint_file_transfer_above_threshold.yaml --- .../Office 365 - sharepoint_file_transfer_above_threshold.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml index f3258d6690..82243b34ae 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml @@ -52,7 +52,7 @@ query: | // Final Output CombinedEvents | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist - | order by TimeGenerated descc + | order by TimeGenerated desc entityMappings: - entityType: Account fieldMappings: From 97e6baada14987ece0c4719a9252fc5e20dfa6ae Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Wed, 2 Oct 2024 14:35:51 +0300 Subject: [PATCH 14/27] package --- .../Global Secure Access/Package/3.0.0.zip | Bin 37346 -> 42645 bytes .../Package/createUiDefinition.json | 74 +- .../Package/mainTemplate.json | 1029 ++++++++--------- 3 files changed, 540 insertions(+), 563 deletions(-) diff --git a/Solutions/Global Secure Access/Package/3.0.0.zip b/Solutions/Global Secure Access/Package/3.0.0.zip index eb1e288242796a29fddb3e8fff4e2e3155d97d28..e33be3976441c5ae8cd76bcac237e6867da12c7c 100644 GIT binary patch literal 42645 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 I%m01*f3g#iKL7v# literal 37346 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 H{oVZ^LrN|) diff --git a/Solutions/Global Secure Access/Package/createUiDefinition.json b/Solutions/Global Secure Access/Package/createUiDefinition.json index dec8e52b06..82cdf541e4 100644 --- a/Solutions/Global Secure Access/Package/createUiDefinition.json +++ b/Solutions/Global Secure Access/Package/createUiDefinition.json @@ -164,13 +164,13 @@ { "name": "analytic3", "type": "Microsoft.Common.Section", - "label": "Office 365 - Exchange AuditLog Disabled", + "label": "GSA Enriched Office 365 - Exchange AuditLog Disabled", "elements": [ { "name": "analytic3-text", "type": "Microsoft.Common.TextBlock", "options": { - "text": "Identifies when the exchange audit logging has been disabled which may be an adversary attempt to evade detection or avoid other defenses." + "text": "Identifies when the Exchange audit logging has been disabled, which may indicate an adversary attempt to evade detection or bypass other defenses." } } ] @@ -178,7 +178,7 @@ { "name": "analytic4", "type": "Microsoft.Common.Section", - "label": "Office 365 - Accessed files shared by temporary external user", + "label": "GSA Enriched Office 365 - Accessed files shared by temporary external user", "elements": [ { "name": "analytic4-text", @@ -192,7 +192,7 @@ { "name": "analytic5", "type": "Microsoft.Common.Section", - "label": "Office 365 - External User Added and Removed in Short Timeframe", + "label": "GSA Enriched Office 365 - External User Added and Removed in Short Timeframe", "elements": [ { "name": "analytic5-text", @@ -206,13 +206,13 @@ { "name": "analytic6", "type": "Microsoft.Common.Section", - "label": "Office 365 - Mail redirect via ExO transport rule", + "label": "GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule", "elements": [ { "name": "analytic6-text", "type": "Microsoft.Common.TextBlock", "options": { - "text": "Identifies when Exchange Online transport rule configured to forward emails.\nThis could be an adversary mailbox configured to collect mail from multiple user accounts." + "text": "Identifies when an Exchange Online transport rule is configured to forward emails.\nThis could indicate an adversary mailbox configured to collect mail from multiple user accounts." } } ] @@ -220,13 +220,13 @@ { "name": "analytic7", "type": "Microsoft.Common.Section", - "label": "Office 365 - Malicious Inbox Rule", + "label": "GSA Enriched Office 365 - Malicious Inbox Rule", "elements": [ { "name": "analytic7-text", "type": "Microsoft.Common.TextBlock", "options": { - "text": "Often times after the initial compromise the attackers create inbox rules to delete emails that contain certain keywords.\n This is done so as to limit ability to warn compromised users that they've been compromised. Below is a sample query that tries to detect this.\nReference: https://www.reddit.com/r/sysadmin/comments/7kyp0a/recent_phishing_attempts_my_experience_and_what/" + "text": "Often times after the initial compromise the attackers create inbox rules to delete emails that contain certain keywords.\nThis is done so as to limit ability to warn compromised users that they've been compromised. Below is a sample query that tries to detect this.\nReference: https://www.reddit.com/r/sysadmin/comments/7kyp0a/recent_phishing_attempts_my_experience_and_what/" } } ] @@ -234,13 +234,13 @@ { "name": "analytic8", "type": "Microsoft.Common.Section", - "label": "Office 365 - Multiple Teams deleted by a single user", + "label": "GSA Enriched Office 365 - Multiple Teams deleted by a single user", "elements": [ { "name": "analytic8-text", "type": "Microsoft.Common.TextBlock", "options": { - "text": "This detection flags the occurrences of deleting multiple teams within an hour.\nThis data is a part of Office 365 Connector in Microsoft Sentinel." + "text": "This detection flags the occurrences of deleting multiple teams within a day.\nThis data is a part of Office 365 Connector in Microsoft Sentinel." } } ] @@ -248,7 +248,7 @@ { "name": "analytic9", "type": "Microsoft.Common.Section", - "label": "Office 365 - Multiple Users Email Forwarded to Same Destination", + "label": "GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination", "elements": [ { "name": "analytic9-text", @@ -262,7 +262,7 @@ { "name": "analytic10", "type": "Microsoft.Common.Section", - "label": "Office 365 - Office Policy Tampering", + "label": "GSA Enriched Office 365 - Office Policy Tampering", "elements": [ { "name": "analytic10-text", @@ -276,7 +276,7 @@ { "name": "analytic11", "type": "Microsoft.Common.Section", - "label": "Office 365 - New Executable via Office FileUploaded Operation", + "label": "GSA Enriched Office 365 - New Executable via Office FileUploaded Operation", "elements": [ { "name": "analytic11-text", @@ -290,7 +290,7 @@ { "name": "analytic12", "type": "Microsoft.Common.Section", - "label": "Office 365 - Rare and Potentially High-Risk Office Operations", + "label": "GSA Enriched Office 365 - Rare and Potentially High-Risk Office Operations", "elements": [ { "name": "analytic12-text", @@ -304,7 +304,7 @@ { "name": "analytic13", "type": "Microsoft.Common.Section", - "label": "Office 365 - SharePoint File Operation via Previously Unseen IPs", + "label": "GSA Enriched Office 365 - SharePoint File Operation via Previously Unseen IPs", "elements": [ { "name": "analytic13-text", @@ -318,7 +318,7 @@ { "name": "analytic14", "type": "Microsoft.Common.Section", - "label": "Office 365 - SharePointFileOperation via devices with previously unseen user agents", + "label": "GSA Enriched Office 365 - SharePointFileOperation via devices with previously unseen user agents", "elements": [ { "name": "analytic14-text", @@ -332,7 +332,7 @@ { "name": "analytic15", "type": "Microsoft.Common.Section", - "label": "Office 365 - Sharepoint File Transfer Above Threshold", + "label": "GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold", "elements": [ { "name": "analytic15-text", @@ -346,13 +346,13 @@ { "name": "analytic16", "type": "Microsoft.Common.Section", - "label": "Office 365 - Sharepoint File Transfer Above Threshold", + "label": "GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold", "elements": [ { "name": "analytic16-text", "type": "Microsoft.Common.TextBlock", "options": { - "text": "Identifies Office365 Sharepoint File Transfers with a distinct folder count above a certain threshold in a 15-minute time period.\nPlease note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur." + "text": "Identifies Office365 Sharepoint File Transfers with a distinct folder count above a certain threshold in a 15-minute time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur." } } ] @@ -426,7 +426,7 @@ { "name": "huntingquery1", "type": "Microsoft.Common.Section", - "label": "Anomalous access to other users' mailboxes", + "label": "GSA Enriched Office 365 - Anomalous access to other users' mailboxes", "elements": [ { "name": "huntingquery1-text", @@ -454,7 +454,7 @@ { "name": "huntingquery3", "type": "Microsoft.Common.Section", - "label": "External user from a new organisation added to Teams", + "label": "GSA Enriched Office 365 - External user from a new organisation added to Teams", "elements": [ { "name": "huntingquery3-text", @@ -468,7 +468,7 @@ { "name": "huntingquery4", "type": "Microsoft.Common.Section", - "label": "Mail Redirect via ExO Transport Rule", + "label": "GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule", "elements": [ { "name": "huntingquery4-text", @@ -482,7 +482,7 @@ { "name": "huntingquery5", "type": "Microsoft.Common.Section", - "label": "Bots added to multiple teams", + "label": "GSA Enriched Office 365 - Bots added to multiple teams", "elements": [ { "name": "huntingquery5-text", @@ -496,7 +496,7 @@ { "name": "huntingquery6", "type": "Microsoft.Common.Section", - "label": "User made Owner of multiple teams", + "label": "GSA Enriched Office 365 - User made Owner of multiple teams", "elements": [ { "name": "huntingquery6-text", @@ -510,7 +510,7 @@ { "name": "huntingquery7", "type": "Microsoft.Common.Section", - "label": "Multiple Teams deleted by a single user", + "label": "GSA Enriched Office 365 - Multiple Teams deleted by a single user", "elements": [ { "name": "huntingquery7-text", @@ -524,7 +524,7 @@ { "name": "huntingquery8", "type": "Microsoft.Common.Section", - "label": "Previously Unseen Bot or Application Added to Teams", + "label": "GSA Enriched Office 365 - Previously Unseen Bot or Application Added to Teams", "elements": [ { "name": "huntingquery8-text", @@ -538,7 +538,7 @@ { "name": "huntingquery9", "type": "Microsoft.Common.Section", - "label": "New Windows Reserved Filenames staged on Office file services", + "label": "GSA Enriched Office 365 - New Windows Reserved Filenames staged on Office file services", "elements": [ { "name": "huntingquery9-text", @@ -552,7 +552,7 @@ { "name": "huntingquery10", "type": "Microsoft.Common.Section", - "label": "Office Mail Forwarding - Hunting Version", + "label": "GSA Enriched Office 365 - Office Mail Forwarding - Hunting Version", "elements": [ { "name": "huntingquery10-text", @@ -566,7 +566,7 @@ { "name": "huntingquery11", "type": "Microsoft.Common.Section", - "label": "Files uploaded to teams and access summary", + "label": "GSA Enriched Office 365 - Files uploaded to teams and access summary", "elements": [ { "name": "huntingquery11-text", @@ -580,7 +580,7 @@ { "name": "huntingquery12", "type": "Microsoft.Common.Section", - "label": "User added to Teams and immediately uploads file", + "label": "GSA Enriched Office 365 - User added to Teams and immediately uploads file", "elements": [ { "name": "huntingquery12-text", @@ -594,7 +594,7 @@ { "name": "huntingquery13", "type": "Microsoft.Common.Section", - "label": "Windows Reserved Filenames Staged on Office File Services", + "label": "GSA Enriched Office 365 - Windows Reserved Filenames Staged on Office File Services", "elements": [ { "name": "huntingquery13-text", @@ -636,7 +636,7 @@ { "name": "huntingquery16", "type": "Microsoft.Common.Section", - "label": "SharePointFileOperation via previously unseen IPs", + "label": "GSA Enriched Office 365 - SharePointFileOperation via previously unseen IPs", "elements": [ { "name": "huntingquery16-text", @@ -650,7 +650,7 @@ { "name": "huntingquery17", "type": "Microsoft.Common.Section", - "label": "SharePointFileOperation via devices with previously unseen user agents", + "label": "GSA Enriched Office 365 -SharePointFileOperation via devices with previously unseen user agents", "elements": [ { "name": "huntingquery17-text", @@ -664,7 +664,7 @@ { "name": "huntingquery18", "type": "Microsoft.Common.Section", - "label": "Non-owner mailbox login activity", + "label": "GSA Enriched Office 365 - Non-owner mailbox login activity", "elements": [ { "name": "huntingquery18-text", @@ -678,7 +678,7 @@ { "name": "huntingquery19", "type": "Microsoft.Common.Section", - "label": "PowerShell or non-browser mailbox login activity", + "label": "GSA Enriched Office 365 - PowerShell or non-browser mailbox login activity", "elements": [ { "name": "huntingquery19-text", @@ -692,7 +692,7 @@ { "name": "huntingquery20", "type": "Microsoft.Common.Section", - "label": "SharePoint File Operation via Client IP with Previously Unseen User Agents", + "label": "GSA Enriched Office 365 - SharePoint File Operation via Client IP with Previously Unseen User Agents", "elements": [ { "name": "huntingquery20-text", @@ -706,7 +706,7 @@ { "name": "huntingquery21", "type": "Microsoft.Common.Section", - "label": "Multiple Users Email Forwarded to Same Destination", + "label": "GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination", "elements": [ { "name": "huntingquery21-text", diff --git a/Solutions/Global Secure Access/Package/mainTemplate.json b/Solutions/Global Secure Access/Package/mainTemplate.json index 239fc11ce6..4ffc806cb7 100644 --- a/Solutions/Global Secure Access/Package/mainTemplate.json +++ b/Solutions/Global Secure Access/Package/mainTemplate.json @@ -80,102 +80,102 @@ "_analyticRulecontentProductId2": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','57abf863-1c1e-46c6-85b2-35370b712c1e','-', '1.0.0')))]" }, "analyticRuleObject3": { - "analyticRuleVersion3": "2.0.7", + "analyticRuleVersion3": "2.0.8", "_analyticRulecontentId3": "dc451755-8ab3-4059-b805-e454c45d1d44", "analyticRuleId3": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'dc451755-8ab3-4059-b805-e454c45d1d44')]", "analyticRuleTemplateSpecName3": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('dc451755-8ab3-4059-b805-e454c45d1d44')))]", - "_analyticRulecontentProductId3": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','dc451755-8ab3-4059-b805-e454c45d1d44','-', '2.0.7')))]" + "_analyticRulecontentProductId3": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','dc451755-8ab3-4059-b805-e454c45d1d44','-', '2.0.8')))]" }, "analyticRuleObject4": { - "analyticRuleVersion4": "2.1.2", + "analyticRuleVersion4": "2.1.3", "_analyticRulecontentId4": "4d38f80f-6b7d-4a1f-aeaf-e38df637e6ac", "analyticRuleId4": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '4d38f80f-6b7d-4a1f-aeaf-e38df637e6ac')]", "analyticRuleTemplateSpecName4": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('4d38f80f-6b7d-4a1f-aeaf-e38df637e6ac')))]", - "_analyticRulecontentProductId4": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','4d38f80f-6b7d-4a1f-aeaf-e38df637e6ac','-', '2.1.2')))]" + "_analyticRulecontentProductId4": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','4d38f80f-6b7d-4a1f-aeaf-e38df637e6ac','-', '2.1.3')))]" }, "analyticRuleObject5": { - "analyticRuleVersion5": "2.1.3", + "analyticRuleVersion5": "2.1.4", "_analyticRulecontentId5": "1a8f1297-23a4-4f09-a20b-90af8fc3641a", "analyticRuleId5": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '1a8f1297-23a4-4f09-a20b-90af8fc3641a')]", "analyticRuleTemplateSpecName5": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('1a8f1297-23a4-4f09-a20b-90af8fc3641a')))]", - "_analyticRulecontentProductId5": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','1a8f1297-23a4-4f09-a20b-90af8fc3641a','-', '2.1.3')))]" + "_analyticRulecontentProductId5": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','1a8f1297-23a4-4f09-a20b-90af8fc3641a','-', '2.1.4')))]" }, "analyticRuleObject6": { - "analyticRuleVersion6": "2.0.5", + "analyticRuleVersion6": "2.1.4", "_analyticRulecontentId6": "edcfc2e0-3134-434c-8074-9101c530d419", "analyticRuleId6": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'edcfc2e0-3134-434c-8074-9101c530d419')]", "analyticRuleTemplateSpecName6": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('edcfc2e0-3134-434c-8074-9101c530d419')))]", - "_analyticRulecontentProductId6": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','edcfc2e0-3134-434c-8074-9101c530d419','-', '2.0.5')))]" + "_analyticRulecontentProductId6": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','edcfc2e0-3134-434c-8074-9101c530d419','-', '2.1.4')))]" }, "analyticRuleObject7": { - "analyticRuleVersion7": "2.0.5", + "analyticRuleVersion7": "2.0.6", "_analyticRulecontentId7": "a9c76c8d-f60d-49ec-9b1f-bdfee6db3807", "analyticRuleId7": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'a9c76c8d-f60d-49ec-9b1f-bdfee6db3807')]", "analyticRuleTemplateSpecName7": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('a9c76c8d-f60d-49ec-9b1f-bdfee6db3807')))]", - "_analyticRulecontentProductId7": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','a9c76c8d-f60d-49ec-9b1f-bdfee6db3807','-', '2.0.5')))]" + "_analyticRulecontentProductId7": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','a9c76c8d-f60d-49ec-9b1f-bdfee6db3807','-', '2.0.6')))]" }, "analyticRuleObject8": { - "analyticRuleVersion8": "2.0.5", + "analyticRuleVersion8": "2.0.6", "_analyticRulecontentId8": "db60e4b6-a845-4f28-a18c-94ebbaad6c6c", "analyticRuleId8": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'db60e4b6-a845-4f28-a18c-94ebbaad6c6c')]", "analyticRuleTemplateSpecName8": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('db60e4b6-a845-4f28-a18c-94ebbaad6c6c')))]", - "_analyticRulecontentProductId8": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','db60e4b6-a845-4f28-a18c-94ebbaad6c6c','-', '2.0.5')))]" + "_analyticRulecontentProductId8": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','db60e4b6-a845-4f28-a18c-94ebbaad6c6c','-', '2.0.6')))]" }, "analyticRuleObject9": { - "analyticRuleVersion9": "2.0.4", + "analyticRuleVersion9": "2.0.5", "_analyticRulecontentId9": "d75e8289-d1cb-44d4-bd59-2f44a9172478", "analyticRuleId9": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'd75e8289-d1cb-44d4-bd59-2f44a9172478')]", "analyticRuleTemplateSpecName9": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('d75e8289-d1cb-44d4-bd59-2f44a9172478')))]", - "_analyticRulecontentProductId9": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','d75e8289-d1cb-44d4-bd59-2f44a9172478','-', '2.0.4')))]" + "_analyticRulecontentProductId9": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','d75e8289-d1cb-44d4-bd59-2f44a9172478','-', '2.0.5')))]" }, "analyticRuleObject10": { - "analyticRuleVersion10": "2.0.4", + "analyticRuleVersion10": "2.0.6", "_analyticRulecontentId10": "0f1f2b17-f9d6-4d2a-a0fb-a7ae1659e3eb", "analyticRuleId10": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '0f1f2b17-f9d6-4d2a-a0fb-a7ae1659e3eb')]", "analyticRuleTemplateSpecName10": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('0f1f2b17-f9d6-4d2a-a0fb-a7ae1659e3eb')))]", - "_analyticRulecontentProductId10": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','0f1f2b17-f9d6-4d2a-a0fb-a7ae1659e3eb','-', '2.0.4')))]" + "_analyticRulecontentProductId10": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','0f1f2b17-f9d6-4d2a-a0fb-a7ae1659e3eb','-', '2.0.6')))]" }, "analyticRuleObject11": { - "analyticRuleVersion11": "2.0.6", + "analyticRuleVersion11": "2.0.7", "_analyticRulecontentId11": "178c62b4-d5e5-40f5-8eab-7fccd0051e7a", "analyticRuleId11": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '178c62b4-d5e5-40f5-8eab-7fccd0051e7a')]", "analyticRuleTemplateSpecName11": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('178c62b4-d5e5-40f5-8eab-7fccd0051e7a')))]", - "_analyticRulecontentProductId11": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','178c62b4-d5e5-40f5-8eab-7fccd0051e7a','-', '2.0.6')))]" + "_analyticRulecontentProductId11": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','178c62b4-d5e5-40f5-8eab-7fccd0051e7a','-', '2.0.7')))]" }, "analyticRuleObject12": { - "analyticRuleVersion12": "2.0.6", + "analyticRuleVersion12": "2.0.7", "_analyticRulecontentId12": "433c254d-4b84-46f7-99ec-9dfefb5f6a7b", "analyticRuleId12": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '433c254d-4b84-46f7-99ec-9dfefb5f6a7b')]", "analyticRuleTemplateSpecName12": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('433c254d-4b84-46f7-99ec-9dfefb5f6a7b')))]", - "_analyticRulecontentProductId12": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','433c254d-4b84-46f7-99ec-9dfefb5f6a7b','-', '2.0.6')))]" + "_analyticRulecontentProductId12": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','433c254d-4b84-46f7-99ec-9dfefb5f6a7b','-', '2.0.7')))]" }, "analyticRuleObject13": { - "analyticRuleVersion13": "2.0.5", + "analyticRuleVersion13": "2.0.6", "_analyticRulecontentId13": "7460e34e-4c99-47b2-b7c0-c42e339fc586", "analyticRuleId13": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '7460e34e-4c99-47b2-b7c0-c42e339fc586')]", "analyticRuleTemplateSpecName13": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('7460e34e-4c99-47b2-b7c0-c42e339fc586')))]", - "_analyticRulecontentProductId13": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','7460e34e-4c99-47b2-b7c0-c42e339fc586','-', '2.0.5')))]" + "_analyticRulecontentProductId13": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','7460e34e-4c99-47b2-b7c0-c42e339fc586','-', '2.0.6')))]" }, "analyticRuleObject14": { - "analyticRuleVersion14": "2.2.5", + "analyticRuleVersion14": "2.2.6", "_analyticRulecontentId14": "efd17c5f-5167-40f8-a1e9-0818940785d9", "analyticRuleId14": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'efd17c5f-5167-40f8-a1e9-0818940785d9')]", "analyticRuleTemplateSpecName14": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('efd17c5f-5167-40f8-a1e9-0818940785d9')))]", - "_analyticRulecontentProductId14": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','efd17c5f-5167-40f8-a1e9-0818940785d9','-', '2.2.5')))]" + "_analyticRulecontentProductId14": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','efd17c5f-5167-40f8-a1e9-0818940785d9','-', '2.2.6')))]" }, "analyticRuleObject15": { - "analyticRuleVersion15": "1.0.5", + "analyticRuleVersion15": "2.0.7", "_analyticRulecontentId15": "30375d00-68cc-4f95-b89a-68064d566358", "analyticRuleId15": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '30375d00-68cc-4f95-b89a-68064d566358')]", "analyticRuleTemplateSpecName15": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('30375d00-68cc-4f95-b89a-68064d566358')))]", - "_analyticRulecontentProductId15": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','30375d00-68cc-4f95-b89a-68064d566358','-', '1.0.5')))]" + "_analyticRulecontentProductId15": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','30375d00-68cc-4f95-b89a-68064d566358','-', '2.0.7')))]" }, "analyticRuleObject16": { - "analyticRuleVersion16": "1.0.5", + "analyticRuleVersion16": "2.0.7", "_analyticRulecontentId16": "abd6976d-8f71-4851-98c4-4d086201319c", "analyticRuleId16": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'abd6976d-8f71-4851-98c4-4d086201319c')]", "analyticRuleTemplateSpecName16": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('abd6976d-8f71-4851-98c4-4d086201319c')))]", - "_analyticRulecontentProductId16": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','abd6976d-8f71-4851-98c4-4d086201319c','-', '1.0.5')))]" + "_analyticRulecontentProductId16": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','abd6976d-8f71-4851-98c4-4d086201319c','-', '2.0.7')))]" }, "analyticRuleObject17": { "analyticRuleVersion17": "1.0.0", @@ -199,7 +199,7 @@ "_analyticRulecontentProductId19": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','82cfa6b9-5f7e-4b8b-8b2f-a63f21b7a7d1','-', '1.0.0')))]" }, "huntingQueryObject1": { - "huntingQueryVersion1": "2.0.1", + "huntingQueryVersion1": "2.0.2", "_huntingQuerycontentId1": "271e8881-3044-4332-a5f4-42264c2e0315", "huntingQueryTemplateSpecName1": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('271e8881-3044-4332-a5f4-42264c2e0315')))]" }, @@ -209,12 +209,12 @@ "huntingQueryTemplateSpecName2": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('119d9e1c-afcc-4d23-b239-cdb4e7bf851c')))]" }, "huntingQueryObject3": { - "huntingQueryVersion3": "2.0.1", + "huntingQueryVersion3": "2.0.2", "_huntingQuerycontentId3": "6fce5baf-bfc2-4c56-a6b7-9c4733fc5a45", "huntingQueryTemplateSpecName3": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('6fce5baf-bfc2-4c56-a6b7-9c4733fc5a45')))]" }, "huntingQueryObject4": { - "huntingQueryVersion4": "2.0.1", + "huntingQueryVersion4": "2.0.2", "_huntingQuerycontentId4": "9891684a-1e3a-4546-9403-3439513cbc70", "huntingQueryTemplateSpecName4": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('9891684a-1e3a-4546-9403-3439513cbc70')))]" }, @@ -224,42 +224,42 @@ "huntingQueryTemplateSpecName5": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('9eb64924-ec8d-44d0-b1f2-10665150fb74')))]" }, "huntingQueryObject6": { - "huntingQueryVersion6": "2.0.1", + "huntingQueryVersion6": "2.0.2", "_huntingQuerycontentId6": "558f15dd-3171-4b11-bf24-31c0610a20e0", "huntingQueryTemplateSpecName6": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('558f15dd-3171-4b11-bf24-31c0610a20e0')))]" }, "huntingQueryObject7": { - "huntingQueryVersion7": "2.0.1", + "huntingQueryVersion7": "2.0.2", "_huntingQuerycontentId7": "64990414-b015-4edf-bef0-343b741e68c5", "huntingQueryTemplateSpecName7": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('64990414-b015-4edf-bef0-343b741e68c5')))]" }, "huntingQueryObject8": { - "huntingQueryVersion8": "2.0.1", + "huntingQueryVersion8": "2.0.2", "_huntingQuerycontentId8": "bf76e508-9282-4cf1-9cc1-5c20c3dea2ee", "huntingQueryTemplateSpecName8": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('bf76e508-9282-4cf1-9cc1-5c20c3dea2ee')))]" }, "huntingQueryObject9": { - "huntingQueryVersion9": "2.0.1", + "huntingQueryVersion9": "2.0.2", "_huntingQuerycontentId9": "641ecd2d-27c9-4f05-8433-8205096b09fc", "huntingQueryTemplateSpecName9": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('641ecd2d-27c9-4f05-8433-8205096b09fc')))]" }, "huntingQueryObject10": { - "huntingQueryVersion10": "2.0.1", + "huntingQueryVersion10": "2.0.2", "_huntingQuerycontentId10": "d49fc965-aef3-49f6-89ad-10cc4697eb5b", "huntingQueryTemplateSpecName10": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('d49fc965-aef3-49f6-89ad-10cc4697eb5b')))]" }, "huntingQueryObject11": { - "huntingQueryVersion11": "2.0.1", + "huntingQueryVersion11": "2.0.2", "_huntingQuerycontentId11": "90e198a9-efb6-4719-ad89-81b8e93633a7", "huntingQueryTemplateSpecName11": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('90e198a9-efb6-4719-ad89-81b8e93633a7')))]" }, "huntingQueryObject12": { - "huntingQueryVersion12": "2.0.1", + "huntingQueryVersion12": "2.0.2", "_huntingQuerycontentId12": "3d6d0c04-7337-40cf-ace6-c471d442356d", "huntingQueryTemplateSpecName12": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('3d6d0c04-7337-40cf-ace6-c471d442356d')))]" }, "huntingQueryObject13": { - "huntingQueryVersion13": "2.0.1", + "huntingQueryVersion13": "2.0.2", "_huntingQuerycontentId13": "61c28cd7-3139-4731-8ea7-2cbbeabb4684", "huntingQueryTemplateSpecName13": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('61c28cd7-3139-4731-8ea7-2cbbeabb4684')))]" }, @@ -274,12 +274,12 @@ "huntingQueryTemplateSpecName15": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('723c5f46-133f-4f1e-ada6-5c138f811d75')))]" }, "huntingQueryObject16": { - "huntingQueryVersion16": "2.0.1", + "huntingQueryVersion16": "2.0.2", "_huntingQuerycontentId16": "e3d24cfd-b2a1-4ba7-8f80-0360892f9d57", "huntingQueryTemplateSpecName16": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('e3d24cfd-b2a1-4ba7-8f80-0360892f9d57')))]" }, "huntingQueryObject17": { - "huntingQueryVersion17": "2.0.1", + "huntingQueryVersion17": "2.0.2", "_huntingQuerycontentId17": "f2367171-1514-4c67-88ef-27434b6a1093", "huntingQueryTemplateSpecName17": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('f2367171-1514-4c67-88ef-27434b6a1093')))]" }, @@ -289,17 +289,17 @@ "huntingQueryTemplateSpecName18": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('0a8f410d-38b5-4d75-90da-32b472b97230')))]" }, "huntingQueryObject19": { - "huntingQueryVersion19": "2.0.1", + "huntingQueryVersion19": "2.0.2", "_huntingQuerycontentId19": "49a4f65a-fe18-408e-afec-042fde93d3ce", "huntingQueryTemplateSpecName19": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('49a4f65a-fe18-408e-afec-042fde93d3ce')))]" }, "huntingQueryObject20": { - "huntingQueryVersion20": "2.0.1", + "huntingQueryVersion20": "2.0.2", "_huntingQuerycontentId20": "e8ae1375-4640-430c-ae8e-2514d09c71eb", "huntingQueryTemplateSpecName20": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('e8ae1375-4640-430c-ae8e-2514d09c71eb')))]" }, "huntingQueryObject21": { - "huntingQueryVersion21": "2.0.1", + "huntingQueryVersion21": "2.0.2", "_huntingQuerycontentId21": "a1551ae4-f61c-4bca-9c57-4d0d681db2e9", "huntingQueryTemplateSpecName21": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('a1551ae4-f61c-4bca-9c57-4d0d681db2e9')))]" }, @@ -511,10 +511,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -526,22 +526,22 @@ ], "entityMappings": [ { - "entityType": "Account", "fieldMappings": [ { - "identifier": "Name", - "columnName": "AccountCustomEntity" + "columnName": "AccountCustomEntity", + "identifier": "Name" } - ] + ], + "entityType": "Account" }, { - "entityType": "IP", "fieldMappings": [ { - "identifier": "Address", - "columnName": "IPCustomEntity" + "columnName": "IPCustomEntity", + "identifier": "Address" } - ] + ], + "entityType": "IP" } ] } @@ -625,10 +625,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -640,22 +640,22 @@ ], "entityMappings": [ { - "entityType": "Account", "fieldMappings": [ { - "identifier": "Name", - "columnName": "AccountCustomEntity" + "columnName": "AccountCustomEntity", + "identifier": "Name" } - ] + ], + "entityType": "Account" }, { - "entityType": "IP", "fieldMappings": [ { - "identifier": "Address", - "columnName": "IPCustomEntity" + "columnName": "IPCustomEntity", + "identifier": "Address" } - ] + ], + "entityType": "IP" } ] } @@ -725,10 +725,10 @@ "kind": "Scheduled", "location": "[parameters('workspace-location')]", "properties": { - "description": "Identifies when the exchange audit logging has been disabled which may be an adversary attempt to evade detection or avoid other defenses.", - "displayName": "Office 365 - Exchange AuditLog Disabled", + "description": "Identifies when the Exchange audit logging has been disabled, which may indicate an adversary attempt to evade detection or bypass other defenses.", + "displayName": "GSA Enriched Office 365 - Exchange AuditLog Disabled", "enabled": false, - "query": "EnrichedMicrosoft365AuditLogs\n| where Workload =~ \"Exchange\"\n| where UserType in~ (\"Admin\", \"DcAdmin\")\n| where Operation =~ \"Set-AdminAuditLogConfig\"\n| extend AdminAuditLogEnabledValue = tostring(parse_json(tostring(parse_json(tostring(array_slice(parse_json(tostring(AdditionalProperties.Parameters)), 3, 3)))[0])).Value)\n| where AdminAuditLogEnabledValue =~ \"False\"\n| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() by Operation, UserType, UserId, ClientIP = SourceIp, ResultStatus, Parameters = tostring(AdditionalProperties.Parameters), AdminAuditLogEnabledValue\n| extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), UserId)\n| extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '')\n| extend AccountName = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[1]), AccountName)\n| extend AccountNTDomain = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[0]), '')\n", + "query": "// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"Exchange\" \n | where UserType in~ (\"Admin\", \"DcAdmin\")\n // Only admin or global-admin can disable audit logging\n | where Operation =~ \"Set-AdminAuditLogConfig\"\n | extend ParsedParameters = parse_json(Parameters)\n | extend AdminAuditLogEnabledValue = tostring(ParsedParameters[3].Value)\n | where AdminAuditLogEnabledValue =~ \"False\"\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() \n by Operation, UserType, UserId, ClientIP, ResultStatus, Parameters, AdminAuditLogEnabledValue\n | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), \n iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[1]), UserId))\n | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '')\n | extend AccountNTDomain = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[0]), '');\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"Exchange\"\n | where UserType in~ (\"Admin\", \"DcAdmin\")\n | where Operation =~ \"Set-AdminAuditLogConfig\"\n | extend ParsedParameters = parse_json(AdditionalProperties.Parameters)\n | extend AdminAuditLogEnabledValue = tostring(ParsedParameters[3].Value)\n | where AdminAuditLogEnabledValue =~ \"False\"\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() \n by Operation, UserType, UserId, ClientIP = SourceIp, ResultStatus, Parameters = tostring(AdditionalProperties.Parameters), AdminAuditLogEnabledValue\n | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), \n iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[1]), UserId))\n | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '')\n | extend AccountNTDomain = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[0]), '');\n// Combine Office and Enriched Events and Deduplicate\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTimeUtc, *) by Operation, UserId, ClientIP;\n// Project Final Output\nCombinedEvents\n | project StartTimeUtc, EndTimeUtc, Operation, UserType, UserId, ClientIP, ResultStatus, Parameters, AdminAuditLogEnabledValue, AccountName, AccountUPNSuffix, AccountNTDomain\n", "queryFrequency": "P1D", "queryPeriod": "P1D", "severity": "Medium", @@ -739,10 +739,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -753,39 +753,30 @@ ], "entityMappings": [ { - "entityType": "Account", "fieldMappings": [ { - "identifier": "FullName", - "columnName": "UserId" + "columnName": "UserId", + "identifier": "FullName" }, { - "identifier": "Name", - "columnName": "AccountName" + "columnName": "AccountName", + "identifier": "Name" }, { - "identifier": "UPNSuffix", - "columnName": "AccountUPNSuffix" - } - ] - }, - { - "entityType": "Account", - "fieldMappings": [ - { - "identifier": "Name", - "columnName": "AccountNTDomain" + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" } - ] + ], + "entityType": "Account" }, { - "entityType": "IP", "fieldMappings": [ { - "identifier": "Address", - "columnName": "ClientIP" + "columnName": "ClientIP", + "identifier": "Address" } - ] + ], + "entityType": "IP" } ] } @@ -826,7 +817,7 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('analyticRuleObject3')._analyticRulecontentId3]", "contentKind": "AnalyticsRule", - "displayName": "Office 365 - Exchange AuditLog Disabled", + "displayName": "GSA Enriched Office 365 - Exchange AuditLog Disabled", "contentProductId": "[variables('analyticRuleObject3')._analyticRulecontentProductId3]", "id": "[variables('analyticRuleObject3')._analyticRulecontentProductId3]", "version": "[variables('analyticRuleObject3').analyticRuleVersion3]" @@ -856,9 +847,9 @@ "location": "[parameters('workspace-location')]", "properties": { "description": "This detection identifies when an external user is added to a Team or Teams chat and shares a file which is accessed by many users (>10) and the users is removed within short period of time. This might be an indicator of suspicious activity.", - "displayName": "Office 365 - Accessed files shared by temporary external user", + "displayName": "GSA Enriched Office 365 - Accessed files shared by temporary external user", "enabled": false, - "query": "let fileAccessThreshold = 10;\nEnrichedMicrosoft365AuditLogs\n| where Workload =~ \"MicrosoftTeams\"\n| where Operation =~ \"MemberAdded\"\n| extend MemberAdded = tostring(parse_json(tostring(AdditionalProperties)).Members[0].UPN)\n| where MemberAdded contains \"#EXT#\"\n| project TimeAdded = TimeGenerated, Operation, MemberAdded, UserWhoAdded = UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n| join kind=inner (\n EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberRemoved\"\n | extend MemberAdded = tostring(parse_json(tostring(AdditionalProperties)).Members[0].UPN)\n | where MemberAdded contains \"#EXT#\"\n | project TimeDeleted = TimeGenerated, Operation, MemberAdded, UserWhoDeleted = UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n) on MemberAdded, TeamName\n| where TimeDeleted > TimeAdded\n| join kind=inner (\n EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation == \"FileUploaded\"\n | extend MemberAdded = UserId, SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl), TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | join kind=inner (\n EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation == \"FileAccessed\"\n | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl), TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | summarize FileAccessCount = count() by ObjectId, TeamName\n | where FileAccessCount > fileAccessThreshold\n ) on ObjectId, TeamName\n) on MemberAdded, TeamName\n| project-away MemberAdded1, MemberAdded2, ObjectId1, Operation1, Operation2\n| extend MemberAddedAccountName = tostring(split(MemberAdded, \"@\")[0]), MemberAddedAccountUPNSuffix = tostring(split(MemberAdded, \"@\")[1])\n| extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, \"@\")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, \"@\")[1])\n| extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, \"@\")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, \"@\")[1])\n", + "query": "let fileAccessThreshold = 10;\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberAdded\"\n | extend MemberAdded = tostring(parse_json(Members)[0].UPN)\n | where MemberAdded contains \"#EXT#\"\n | project TimeAdded = TimeGenerated, Operation, MemberAdded, UserWhoAdded = UserId, TeamName\n | join kind=inner (\n OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberRemoved\"\n | extend MemberAdded = tostring(parse_json(Members)[0].UPN)\n | where MemberAdded contains \"#EXT#\"\n | project TimeDeleted = TimeGenerated, Operation, MemberAdded, UserWhoDeleted = UserId, TeamName\n ) on MemberAdded\n | where TimeDeleted > TimeAdded\n | join kind=inner (\n OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | where Operation == \"FileUploaded\"\n | extend MemberAdded = UserId\n | join kind=inner (\n OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where Operation == \"FileAccessed\"\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | summarize FileAccessCount = count() by OfficeObjectId\n | where FileAccessCount > fileAccessThreshold\n ) on OfficeObjectId\n ) on MemberAdded\n | project TimeAdded, TimeDeleted, MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberAdded\"\n | extend MemberAdded = tostring(parse_json(tostring(AdditionalProperties)).Members[0].UPN)\n | where MemberAdded contains \"#EXT#\"\n | project TimeAdded = TimeGenerated, Operation, MemberAdded, UserWhoAdded = UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n | join kind=inner (\n EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberRemoved\"\n | extend MemberAdded = tostring(parse_json(tostring(AdditionalProperties)).Members[0].UPN)\n | where MemberAdded contains \"#EXT#\"\n | project TimeDeleted = TimeGenerated, Operation, MemberAdded, UserWhoDeleted = UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n ) on MemberAdded, TeamName\n | where TimeDeleted > TimeAdded\n | join kind=inner (\n EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation == \"FileUploaded\"\n | extend MemberAdded = UserId, SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl), TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | join kind=inner (\n EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation == \"FileAccessed\"\n | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl), TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | summarize FileAccessCount = count() by ObjectId, TeamName\n | where FileAccessCount > fileAccessThreshold\n ) on ObjectId, TeamName\n ) on MemberAdded, TeamName\n | project TimeAdded, TimeDeleted, MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName;\n// Combine Office and Enriched Events and Deduplicate\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeAdded, *) by MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName;\n// Project Final Output\nCombinedEvents\n | extend MemberAddedAccountName = tostring(split(MemberAdded, \"@\")[0]), MemberAddedAccountUPNSuffix = tostring(split(MemberAdded, \"@\")[1])\n | extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, \"@\")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, \"@\")[1])\n | extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, \"@\")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, \"@\")[1])\n | project TimeAdded, TimeDeleted, MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName, MemberAddedAccountName, MemberAddedAccountUPNSuffix, UserWhoAddedAccountName, UserWhoAddedAccountUPNSuffix, UserWhoDeletedAccountName, UserWhoDeletedAccountUPNSuffix\n", "queryFrequency": "PT1H", "queryPeriod": "PT1H", "severity": "Low", @@ -869,10 +860,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -883,64 +874,64 @@ ], "entityMappings": [ { - "entityType": "Account", "fieldMappings": [ { - "identifier": "FullName", - "columnName": "MemberAdded" + "columnName": "MemberAdded", + "identifier": "FullName" }, { - "identifier": "Name", - "columnName": "MemberAddedAccountName" + "columnName": "MemberAddedAccountName", + "identifier": "Name" }, { - "identifier": "UPNSuffix", - "columnName": "MemberAddedAccountUPNSuffix" + "columnName": "MemberAddedAccountUPNSuffix", + "identifier": "UPNSuffix" } - ] + ], + "entityType": "Account" }, { - "entityType": "Account", "fieldMappings": [ { - "identifier": "FullName", - "columnName": "UserWhoAdded" + "columnName": "UserWhoAdded", + "identifier": "FullName" }, { - "identifier": "Name", - "columnName": "UserWhoAddedAccountName" + "columnName": "UserWhoAddedAccountName", + "identifier": "Name" }, { - "identifier": "UPNSuffix", - "columnName": "UserWhoAddedAccountUPNSuffix" + "columnName": "UserWhoAddedAccountUPNSuffix", + "identifier": "UPNSuffix" } - ] + ], + "entityType": "Account" }, { - "entityType": "Account", "fieldMappings": [ { - "identifier": "FullName", - "columnName": "UserWhoDeleted" + "columnName": "UserWhoDeleted", + "identifier": "FullName" }, { - "identifier": "Name", - "columnName": "UserWhoDeletedAccountName" + "columnName": "UserWhoDeletedAccountName", + "identifier": "Name" }, { - "identifier": "UPNSuffix", - "columnName": "UserWhoDeletedAccountUPNSuffix" + "columnName": "UserWhoDeletedAccountUPNSuffix", + "identifier": "UPNSuffix" } - ] + ], + "entityType": "Account" }, { - "entityType": "IP", "fieldMappings": [ { - "identifier": "Address", - "columnName": "ClientIP" + "columnName": "ClientIP", + "identifier": "Address" } - ] + ], + "entityType": "IP" } ] } @@ -981,7 +972,7 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('analyticRuleObject4')._analyticRulecontentId4]", "contentKind": "AnalyticsRule", - "displayName": "Office 365 - Accessed files shared by temporary external user", + "displayName": "GSA Enriched Office 365 - Accessed files shared by temporary external user", "contentProductId": "[variables('analyticRuleObject4')._analyticRulecontentProductId4]", "id": "[variables('analyticRuleObject4')._analyticRulecontentProductId4]", "version": "[variables('analyticRuleObject4').analyticRuleVersion4]" @@ -1011,9 +1002,9 @@ "location": "[parameters('workspace-location')]", "properties": { "description": "This detection flags the occurrences of external user accounts that are added to a Team and then removed within one hour.", - "displayName": "Office 365 - External User Added and Removed in Short Timeframe", + "displayName": "GSA Enriched Office 365 - External User Added and Removed in Short Timeframe", "enabled": false, - "query": "let TeamsAddDel = (Op:string){\nEnrichedMicrosoft365AuditLogs\n | where Workload =~ \"MicrosoftTeams\"\n | where Operation == Op\n | where tostring(AdditionalProperties.Members) has (\"#EXT#\")\n | mv-expand Members = parse_json(tostring(AdditionalProperties.Members))\n | extend UPN = tostring(Members.UPN)\n | where UPN has (\"#EXT#\")\n | project TimeGenerated, Operation, UPN, UserId, TeamName = tostring(AdditionalProperties.TeamName), ClientIP = SourceIp\n};\nlet TeamsAdd = TeamsAddDel(\"MemberAdded\")\n| project TimeAdded = TimeGenerated, Operation, MemberAdded = UPN, UserWhoAdded = UserId, TeamName, ClientIP;\nlet TeamsDel = TeamsAddDel(\"MemberRemoved\")\n| project TimeDeleted = TimeGenerated, Operation, MemberRemoved = UPN, UserWhoDeleted = UserId, TeamName, ClientIP;\nTeamsAdd\n| join kind = inner (TeamsDel) on $left.MemberAdded == $right.MemberRemoved\n| where TimeDeleted > TimeAdded\n| project TimeAdded, TimeDeleted, MemberAdded_Removed = MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName\n| extend MemberAdded_RemovedAccountName = tostring(split(MemberAdded_Removed, \"@\")[0]), MemberAddedAccountUPNSuffix = tostring(split(MemberAdded_Removed, \"@\")[1])\n| extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, \"@\")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, \"@\")[1])\n| extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, \"@\")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, \"@\")[1])\n", + "query": "let TeamsAddDelOffice = (Op:string){\n OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation == Op\n | where Members has (\"#EXT#\")\n | mv-expand Members\n | extend UPN = tostring(Members.UPN)\n | where UPN has (\"#EXT#\")\n | project TimeGenerated, Operation, UPN, UserId, TeamName, ClientIP\n};\nlet TeamsAddDelEnriched = (Op:string){\n EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"MicrosoftTeams\"\n | where Operation == Op\n | where tostring(AdditionalProperties.Members) has (\"#EXT#\")\n | mv-expand Members = parse_json(tostring(AdditionalProperties.Members))\n | extend UPN = tostring(Members.UPN)\n | where UPN has (\"#EXT#\")\n | project TimeGenerated, Operation, UPN, UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName), ClientIP = SourceIp\n};\nlet TeamsAddOffice = TeamsAddDelOffice(\"MemberAdded\")\n | project TimeAdded = TimeGenerated, Operation, MemberAdded = UPN, UserWhoAdded = UserId, TeamName, ClientIP;\nlet TeamsDelOffice = TeamsAddDelOffice(\"MemberRemoved\")\n | project TimeDeleted = TimeGenerated, Operation, MemberRemoved = UPN, UserWhoDeleted = UserId, TeamName, ClientIP;\nlet TeamsAddEnriched = TeamsAddDelEnriched(\"MemberAdded\")\n | project TimeAdded = TimeGenerated, Operation, MemberAdded = UPN, UserWhoAdded = UserId, TeamName, ClientIP;\nlet TeamsDelEnriched = TeamsAddDelEnriched(\"MemberRemoved\")\n | project TimeDeleted = TimeGenerated, Operation, MemberRemoved = UPN, UserWhoDeleted = UserId, TeamName, ClientIP;\nlet TeamsAdd = TeamsAddOffice\n | union TeamsAddEnriched\n | project TimeAdded, Operation, MemberAdded, UserWhoAdded, TeamName, ClientIP;\nlet TeamsDel = TeamsDelOffice\n | union TeamsDelEnriched\n | project TimeDeleted, Operation, MemberRemoved, UserWhoDeleted, TeamName, ClientIP;\nTeamsAdd\n| join kind=inner (TeamsDel) on $left.MemberAdded == $right.MemberRemoved\n| where TimeDeleted > TimeAdded\n| project TimeAdded, TimeDeleted, MemberAdded_Removed = MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName, ClientIP\n| extend MemberAdded_RemovedAccountName = tostring(split(MemberAdded_Removed, \"@\")[0]), MemberAdded_RemovedAccountUPNSuffix = tostring(split(MemberAdded_Removed, \"@\")[1])\n| extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, \"@\")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, \"@\")[1])\n| extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, \"@\")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, \"@\")[1])\n", "queryFrequency": "PT1H", "queryPeriod": "PT1H", "severity": "Low", @@ -1024,10 +1015,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -1038,64 +1029,64 @@ ], "entityMappings": [ { - "entityType": "Account", "fieldMappings": [ { - "identifier": "FullName", - "columnName": "MemberAdded_Removed" + "columnName": "MemberAdded_Removed", + "identifier": "FullName" }, { - "identifier": "Name", - "columnName": "MemberAdded_RemovedAccountName" + "columnName": "MemberAdded_RemovedAccountName", + "identifier": "Name" }, { - "identifier": "UPNSuffix", - "columnName": "MemberAddedAccountUPNSuffix" + "columnName": "MemberAdded_RemovedAccountUPNSuffix", + "identifier": "UPNSuffix" } - ] + ], + "entityType": "Account" }, { - "entityType": "Account", "fieldMappings": [ { - "identifier": "FullName", - "columnName": "UserWhoAdded" + "columnName": "UserWhoAdded", + "identifier": "FullName" }, { - "identifier": "Name", - "columnName": "UserWhoAddedAccountName" + "columnName": "UserWhoAddedAccountName", + "identifier": "Name" }, { - "identifier": "UPNSuffix", - "columnName": "UserWhoAddedAccountUPNSuffix" + "columnName": "UserWhoAddedAccountUPNSuffix", + "identifier": "UPNSuffix" } - ] + ], + "entityType": "Account" }, { - "entityType": "Account", "fieldMappings": [ { - "identifier": "FullName", - "columnName": "UserWhoDeleted" + "columnName": "UserWhoDeleted", + "identifier": "FullName" }, { - "identifier": "Name", - "columnName": "UserWhoDeletedAccountName" + "columnName": "UserWhoDeletedAccountName", + "identifier": "Name" }, { - "identifier": "UPNSuffix", - "columnName": "UserWhoDeletedAccountUPNSuffix" + "columnName": "UserWhoDeletedAccountUPNSuffix", + "identifier": "UPNSuffix" } - ] + ], + "entityType": "Account" }, { - "entityType": "IP", "fieldMappings": [ { - "identifier": "Address", - "columnName": "ClientIp" + "columnName": "ClientIP", + "identifier": "Address" } - ] + ], + "entityType": "IP" } ] } @@ -1136,7 +1127,7 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('analyticRuleObject5')._analyticRulecontentId5]", "contentKind": "AnalyticsRule", - "displayName": "Office 365 - External User Added and Removed in Short Timeframe", + "displayName": "GSA Enriched Office 365 - External User Added and Removed in Short Timeframe", "contentProductId": "[variables('analyticRuleObject5')._analyticRulecontentProductId5]", "id": "[variables('analyticRuleObject5')._analyticRulecontentProductId5]", "version": "[variables('analyticRuleObject5').analyticRuleVersion5]" @@ -1165,10 +1156,10 @@ "kind": "Scheduled", "location": "[parameters('workspace-location')]", "properties": { - "description": "Identifies when Exchange Online transport rule configured to forward emails.\nThis could be an adversary mailbox configured to collect mail from multiple user accounts.", - "displayName": "Office 365 - Mail redirect via ExO transport rule", + "description": "Identifies when an Exchange Online transport rule is configured to forward emails.\nThis could indicate an adversary mailbox configured to collect mail from multiple user accounts.", + "displayName": "GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule", "enabled": false, - "query": "EnrichedMicrosoft365AuditLogs\n| where Workload == \"Exchange\"\n| where Operation in~ (\"New-TransportRule\", \"Set-TransportRule\")\n| mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n| extend RuleName = case(\n Operation =~ \"Set-TransportRule\", ObjectId, // Assuming ObjectId maps to what was previously OfficeObjectId\n Operation =~ \"New-TransportRule\", ParsedParameters.Name,\n \"Unknown\"\n )\n| mv-expand ExpandedParameters = todynamic(tostring(AdditionalProperties.Parameters))\n| where ExpandedParameters.Name in~ (\"BlindCopyTo\", \"RedirectMessageTo\") and isnotempty(ExpandedParameters.Value)\n| extend RedirectTo = ExpandedParameters.Value\n| extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIp)[0]\n| extend From = ParsedParameters.From\n| project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters = tostring(AdditionalProperties.Parameters)\n| extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n", + "query": "let officeActivityQuery = OfficeActivity\n | where OfficeWorkload == \"Exchange\"\n | where Operation in~ (\"New-TransportRule\", \"Set-TransportRule\")\n | mv-apply DynamicParameters = todynamic(Parameters) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | extend RuleName = case(\n Operation =~ \"Set-TransportRule\", OfficeObjectId,\n Operation =~ \"New-TransportRule\", ParsedParameters.Name,\n \"Unknown\"\n )\n | mv-expand ExpandedParameters = todynamic(Parameters)\n | where ExpandedParameters.Name in~ (\"BlindCopyTo\", \"RedirectMessageTo\") and isnotempty(ExpandedParameters.Value)\n | extend RedirectTo = ExpandedParameters.Value\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIP)[0]\n | extend From = ParsedParameters.From\n | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters\n | extend AccountName = tostring(split(UserId, \"@\")[0]), \n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\nlet enrichedLogsQuery = EnrichedMicrosoft365AuditLogs\n | where Workload == \"Exchange\"\n | where Operation in~ (\"New-TransportRule\", \"Set-TransportRule\")\n | mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | extend RuleName = case(\n Operation =~ \"Set-TransportRule\", ObjectId, // Assuming ObjectId maps to OfficeObjectId\n Operation =~ \"New-TransportRule\", ParsedParameters.Name,\n \"Unknown\"\n )\n | mv-expand ExpandedParameters = todynamic(tostring(AdditionalProperties.Parameters))\n | where ExpandedParameters.Name in~ (\"BlindCopyTo\", \"RedirectMessageTo\") and isnotempty(ExpandedParameters.Value)\n | extend RedirectTo = ExpandedParameters.Value\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIp)[0]\n | extend From = ParsedParameters.From\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters = tostring(AdditionalProperties.Parameters), UserAgent\n | extend AccountName = tostring(split(UserId, \"@\")[0]), \n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Combine both queries\nunion isfuzzy=true officeActivityQuery, enrichedLogsQuery\n| summarize arg_min(TimeGenerated, *) by RuleName, RedirectTo\n| project TimeGenerated, RedirectTo, IPAddress, Port, UserId, From, Operation, RuleName, Parameters, AccountName, AccountUPNSuffix\n| order by TimeGenerated desc;\n", "queryFrequency": "PT1H", "queryPeriod": "PT1H", "severity": "Medium", @@ -1179,10 +1170,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -1195,30 +1186,30 @@ ], "entityMappings": [ { - "entityType": "Account", "fieldMappings": [ { - "identifier": "FullName", - "columnName": "UserId" + "columnName": "UserId", + "identifier": "FullName" }, { - "identifier": "Name", - "columnName": "AccountName" + "columnName": "AccountName", + "identifier": "Name" }, { - "identifier": "UPNSuffix", - "columnName": "AccountUPNSuffix" + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" } - ] + ], + "entityType": "Account" }, { - "entityType": "IP", "fieldMappings": [ { - "identifier": "Address", - "columnName": "IPAddress" + "columnName": "IPAddress", + "identifier": "Address" } - ] + ], + "entityType": "IP" } ] } @@ -1259,7 +1250,7 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('analyticRuleObject6')._analyticRulecontentId6]", "contentKind": "AnalyticsRule", - "displayName": "Office 365 - Mail redirect via ExO transport rule", + "displayName": "GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule", "contentProductId": "[variables('analyticRuleObject6')._analyticRulecontentProductId6]", "id": "[variables('analyticRuleObject6')._analyticRulecontentProductId6]", "version": "[variables('analyticRuleObject6').analyticRuleVersion6]" @@ -1288,10 +1279,10 @@ "kind": "Scheduled", "location": "[parameters('workspace-location')]", "properties": { - "description": "Often times after the initial compromise the attackers create inbox rules to delete emails that contain certain keywords.\n This is done so as to limit ability to warn compromised users that they've been compromised. Below is a sample query that tries to detect this.\nReference: https://www.reddit.com/r/sysadmin/comments/7kyp0a/recent_phishing_attempts_my_experience_and_what/", - "displayName": "Office 365 - Malicious Inbox Rule", + "description": "Often times after the initial compromise the attackers create inbox rules to delete emails that contain certain keywords.\nThis is done so as to limit ability to warn compromised users that they've been compromised. Below is a sample query that tries to detect this.\nReference: https://www.reddit.com/r/sysadmin/comments/7kyp0a/recent_phishing_attempts_my_experience_and_what/", + "displayName": "GSA Enriched Office 365 - Malicious Inbox Rule", "enabled": false, - "query": "let Keywords = dynamic([\"helpdesk\", \"alert\", \"suspicious\", \"fake\", \"malicious\", \"phishing\", \"spam\", \"do not click\", \"do not open\", \"hijacked\", \"Fatal\"]);\nEnrichedMicrosoft365AuditLogs\n| where Workload =~ \"Exchange\"\n| where Operation =~ \"New-InboxRule\" and (ResultStatus =~ \"True\" or ResultStatus =~ \"Succeeded\")\n| where tostring(parse_json(tostring(AdditionalProperties)).Parameters) has \"Deleted Items\" or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has \"Junk Email\" or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has \"DeleteMessage\"\n| extend Events = parse_json(tostring(AdditionalProperties)).Parameters\n| extend SubjectContainsWords = tostring(Events.SubjectContainsWords), BodyContainsWords = tostring(Events.BodyContainsWords), SubjectOrBodyContainsWords = tostring(Events.SubjectOrBodyContainsWords)\n| where SubjectContainsWords has_any (Keywords) or BodyContainsWords has_any (Keywords) or SubjectOrBodyContainsWords has_any (Keywords)\n| extend ClientIPAddress = case(ClientIp has \".\", tostring(split(ClientIp, \":\")[0]), ClientIp has \"[\", tostring(trim_start(@'[[]',tostring(split(ClientIp, \"]\")[0]))), ClientIp)\n| extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, (iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords)))\n| extend RuleDetail = case(ObjectId contains '/', tostring(split(ObjectId, '/')[-1]), tostring(split(ObjectId, '\\\\')[-1]))\n| summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Operation, UserId, ClientIPAddress, ResultStatus, Keyword, ObjectId, RuleDetail\n| extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n", + "query": "let Keywords = dynamic([\"helpdesk\", \"alert\", \"suspicious\", \"fake\", \"malicious\", \"phishing\", \"spam\", \"do not click\", \"do not open\", \"hijacked\", \"Fatal\"]);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"Exchange\" \n | where Operation =~ \"New-InboxRule\" and (ResultStatus =~ \"True\" or ResultStatus =~ \"Succeeded\")\n | where Parameters has \"Deleted Items\" or Parameters has \"Junk Email\" or Parameters has \"DeleteMessage\"\n | extend Events = todynamic(Parameters)\n | parse Events with * \"SubjectContainsWords\" SubjectContainsWords '}'*\n | parse Events with * \"BodyContainsWords\" BodyContainsWords '}'*\n | parse Events with * \"SubjectOrBodyContainsWords\" SubjectOrBodyContainsWords '}'*\n | where SubjectContainsWords has_any (Keywords)\n or BodyContainsWords has_any (Keywords)\n or SubjectOrBodyContainsWords has_any (Keywords)\n | extend ClientIPAddress = case(\n ClientIP has \".\", tostring(split(ClientIP, \":\")[0]),\n ClientIP has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIP, \"]\")[0]))),\n ClientIP\n )\n | extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords))\n | extend RuleDetail = case(\n OfficeObjectId contains '/', tostring(split(OfficeObjectId, '/')[-1]),\n OfficeObjectId contains '\\\\', tostring(split(OfficeObjectId, '\\\\')[-1]),\n \"Unknown\"\n )\n | summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Operation, UserId, ClientIPAddress, ResultStatus, Keyword, OriginatingServer, OfficeObjectId, RuleDetail\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend OriginatingServerName = tostring(split(OriginatingServer, \" \")[0]);\n\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"Exchange\"\n | where Operation =~ \"New-InboxRule\" and (ResultStatus =~ \"True\" or ResultStatus =~ \"Succeeded\")\n | where tostring(parse_json(tostring(AdditionalProperties)).Parameters) has \"Deleted Items\" \n or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has \"Junk Email\" \n or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has \"DeleteMessage\"\n | extend Events = parse_json(tostring(AdditionalProperties)).Parameters\n | extend SubjectContainsWords = tostring(Events.SubjectContainsWords), \n BodyContainsWords = tostring(Events.BodyContainsWords), \n SubjectOrBodyContainsWords = tostring(Events.SubjectOrBodyContainsWords)\n | where SubjectContainsWords has_any (Keywords) \n or BodyContainsWords has_any (Keywords) \n or SubjectOrBodyContainsWords has_any (Keywords)\n | extend ClientIPAddress = case(\n ClientIp has \".\", tostring(split(ClientIp, \":\")[0]),\n ClientIp has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIp, \"]\")[0]))),\n ClientIp\n )\n | extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords))\n | extend RuleDetail = case(\n ObjectId contains '/', tostring(split(ObjectId, '/')[-1]),\n ObjectId contains '\\\\', tostring(split(ObjectId, '\\\\')[-1]),\n \"Unknown\"\n )\n | summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Operation, UserId, ClientIPAddress, ResultStatus, Keyword, ObjectId, RuleDetail\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Combine and Deduplicate\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTimeUtc, *) by Operation, UserId, ClientIPAddress;\n\n// Final Output\nCombinedEvents\n | project StartTimeUtc, EndTimeUtc, Operation, UserId, ClientIPAddress, ResultStatus, Keyword, RuleDetail, AccountName, AccountUPNSuffix;\n", "queryFrequency": "P1D", "queryPeriod": "P1D", "severity": "Medium", @@ -1302,10 +1293,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -1318,39 +1309,30 @@ ], "entityMappings": [ { - "entityType": "Account", "fieldMappings": [ { - "identifier": "FullName", - "columnName": "UserId" + "columnName": "UserId", + "identifier": "FullName" }, { - "identifier": "Name", - "columnName": "AccountName" + "columnName": "AccountName", + "identifier": "Name" }, { - "identifier": "UPNSuffix", - "columnName": "AccountUPNSuffix" + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" } - ] - }, - { - "entityType": "Host", - "fieldMappings": [ - { - "identifier": "FullName", - "columnName": "OriginatingServer" - } - ] + ], + "entityType": "Account" }, { - "entityType": "IP", "fieldMappings": [ { - "identifier": "Address", - "columnName": "ClientIPAddress" + "columnName": "ClientIPAddress", + "identifier": "Address" } - ] + ], + "entityType": "IP" } ] } @@ -1391,7 +1373,7 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('analyticRuleObject7')._analyticRulecontentId7]", "contentKind": "AnalyticsRule", - "displayName": "Office 365 - Malicious Inbox Rule", + "displayName": "GSA Enriched Office 365 - Malicious Inbox Rule", "contentProductId": "[variables('analyticRuleObject7')._analyticRulecontentProductId7]", "id": "[variables('analyticRuleObject7')._analyticRulecontentProductId7]", "version": "[variables('analyticRuleObject7').analyticRuleVersion7]" @@ -1420,10 +1402,10 @@ "kind": "Scheduled", "location": "[parameters('workspace-location')]", "properties": { - "description": "This detection flags the occurrences of deleting multiple teams within an hour.\nThis data is a part of Office 365 Connector in Microsoft Sentinel.", - "displayName": "Office 365 - Multiple Teams deleted by a single user", + "description": "This detection flags the occurrences of deleting multiple teams within a day.\nThis data is a part of Office 365 Connector in Microsoft Sentinel.", + "displayName": "GSA Enriched Office 365 - Multiple Teams deleted by a single user", "enabled": false, - "query": "let max_delete_count = 3;\nEnrichedMicrosoft365AuditLogs\n| where Workload =~ \"MicrosoftTeams\"\n| where Operation =~ \"TeamDeleted\"\n| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DeletedTeams = make_set(tostring(AdditionalProperties.TeamName), 1000) by UserId\n| where array_length(DeletedTeams) > max_delete_count\n| extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n", + "query": "// Set the maximum number of deleted teams to flag suspicious activity\nlet max_delete_count = 3;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"MicrosoftTeams\"\n | where Operation =~ \"TeamDeleted\"\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DeletedTeams = make_set(tostring(AdditionalProperties.TeamName), 1000) by UserId\n | where array_length(DeletedTeams) > max_delete_count\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"TeamDeleted\"\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DeletedTeams = make_set(TeamName, 1000) by UserId\n | where array_length(DeletedTeams) > max_delete_count\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// Combine and Deduplicate Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by UserId;\n// Final Output\nCombinedEvents\n | project StartTime, EndTime, DeletedTeams, UserId, AccountName, AccountUPNSuffix\n", "queryFrequency": "P1D", "queryPeriod": "P1D", "severity": "Low", @@ -1434,10 +1416,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -1449,21 +1431,21 @@ ], "entityMappings": [ { - "entityType": "Account", "fieldMappings": [ { - "identifier": "FullName", - "columnName": "UserId" + "columnName": "UserId", + "identifier": "FullName" }, { - "identifier": "Name", - "columnName": "AccountName" + "columnName": "AccountName", + "identifier": "Name" }, { - "identifier": "UPNSuffix", - "columnName": "AccountUPNSuffix" + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" } - ] + ], + "entityType": "Account" } ] } @@ -1504,7 +1486,7 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('analyticRuleObject8')._analyticRulecontentId8]", "contentKind": "AnalyticsRule", - "displayName": "Office 365 - Multiple Teams deleted by a single user", + "displayName": "GSA Enriched Office 365 - Multiple Teams deleted by a single user", "contentProductId": "[variables('analyticRuleObject8')._analyticRulecontentProductId8]", "id": "[variables('analyticRuleObject8')._analyticRulecontentProductId8]", "version": "[variables('analyticRuleObject8').analyticRuleVersion8]" @@ -1534,9 +1516,9 @@ "location": "[parameters('workspace-location')]", "properties": { "description": "Identifies when multiple (more than one) users' mailboxes are configured to forward to the same destination. \nThis could be an attacker-controlled destination mailbox configured to collect mail from multiple compromised user accounts.", - "displayName": "Office 365 - Multiple Users Email Forwarded to Same Destination", + "displayName": "GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination", "enabled": false, - "query": "let queryfrequency = 1d;\n let queryperiod = 7d;\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated > ago(queryperiod)\n | where Workload =~ \"Exchange\"\n //| where Operation in (\"Set-Mailbox\", \"New-InboxRule\", \"Set-InboxRule\") // Uncomment or adjust based on actual field usage\n | where tostring(AdditionalProperties.Parameters) has_any (\"ForwardTo\", \"RedirectTo\", \"ForwardingSmtpAddress\")\n | mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on (\n summarize ParsedParameters = make_bag(bag_pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source')\n | extend DestinationMailAddress = tolower(case(\n isnotempty(column_ifexists(\"ForwardTo\", \"\")), column_ifexists(\"ForwardTo\", \"\"),\n isnotempty(column_ifexists(\"RedirectTo\", \"\")), column_ifexists(\"RedirectTo\", \"\"),\n isnotempty(column_ifexists(\"ForwardingSmtpAddress\", \"\")), trim_start(@\"smtp:\", column_ifexists(\"ForwardingSmtpAddress\", \"\")),\n \"\"))\n | where isnotempty(DestinationMailAddress)\n | mv-expand split(DestinationMailAddress, \";\")\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIp)[0]\n | extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1])\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP\n | where DistinctUserCount > 1 and EndTime > ago(queryfrequency)\n | mv-expand UserId to typeof(string)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n", + "query": "// Set query parameters\nlet queryfrequency = 1d;\nlet queryperiod = 7d;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated > ago(queryperiod)\n | where Workload =~ \"Exchange\"\n // Uncomment or adjust the following line based on actual field usage\n // | where Operation in (\"Set-Mailbox\", \"New-InboxRule\", \"Set-InboxRule\")\n | where tostring(AdditionalProperties.Parameters) has_any (\"ForwardTo\", \"RedirectTo\", \"ForwardingSmtpAddress\")\n | mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source')\n | extend DestinationMailAddress = tolower(case(\n isnotempty(column_ifexists(\"ForwardTo\", \"\")), column_ifexists(\"ForwardTo\", \"\"),\n isnotempty(column_ifexists(\"RedirectTo\", \"\")), column_ifexists(\"RedirectTo\", \"\"),\n isnotempty(column_ifexists(\"ForwardingSmtpAddress\", \"\")), trim_start(@\"smtp:\", column_ifexists(\"ForwardingSmtpAddress\", \"\")),\n \"\"))\n | where isnotempty(DestinationMailAddress)\n | mv-expand split(DestinationMailAddress, \";\")\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIp)[0]\n | extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1])\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP\n | where DistinctUserCount > 1 and EndTime > ago(queryfrequency)\n | mv-expand UserId to typeof(string)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated > ago(queryperiod)\n | where OfficeWorkload =~ \"Exchange\"\n // Uncomment or adjust the following line based on actual field usage\n // | where Operation in (\"Set-Mailbox\", \"New-InboxRule\", \"Set-InboxRule\")\n | where Parameters has_any (\"ForwardTo\", \"RedirectTo\", \"ForwardingSmtpAddress\")\n | mv-apply DynamicParameters = todynamic(Parameters) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source')\n | extend DestinationMailAddress = tolower(case(\n isnotempty(column_ifexists(\"ForwardTo\", \"\")), column_ifexists(\"ForwardTo\", \"\"),\n isnotempty(column_ifexists(\"RedirectTo\", \"\")), column_ifexists(\"RedirectTo\", \"\"),\n isnotempty(column_ifexists(\"ForwardingSmtpAddress\", \"\")), trim_start(@\"smtp:\", column_ifexists(\"ForwardingSmtpAddress\", \"\")),\n \"\"))\n | where isnotempty(DestinationMailAddress)\n | mv-expand split(DestinationMailAddress, \";\")\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIP)[0]\n | extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1])\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP\n | where DistinctUserCount > 1 and EndTime > ago(queryfrequency)\n | mv-expand UserId to typeof(string)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// Combine and Deduplicate Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by tostring(DestinationMailAddress), ClientIP;\n// Final Output\nCombinedEvents\n | project StartTime, EndTime, DestinationMailAddress, ClientIP, DistinctUserCount, UserId, Ports, EventCount, AccountName, AccountUPNSuffix\n", "queryFrequency": "P1D", "queryPeriod": "P7D", "severity": "Medium", @@ -1547,10 +1529,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -1563,30 +1545,30 @@ ], "entityMappings": [ { - "entityType": "Account", "fieldMappings": [ { - "identifier": "FullName", - "columnName": "UserId" + "columnName": "UserId", + "identifier": "FullName" }, { - "identifier": "Name", - "columnName": "AccountName" + "columnName": "AccountName", + "identifier": "Name" }, { - "identifier": "UPNSuffix", - "columnName": "AccountUPNSuffix" + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" } - ] + ], + "entityType": "Account" }, { - "entityType": "IP", "fieldMappings": [ { - "identifier": "Address", - "columnName": "ClientIP" + "columnName": "ClientIP", + "identifier": "Address" } - ] + ], + "entityType": "IP" } ] } @@ -1627,7 +1609,7 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('analyticRuleObject9')._analyticRulecontentId9]", "contentKind": "AnalyticsRule", - "displayName": "Office 365 - Multiple Users Email Forwarded to Same Destination", + "displayName": "GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination", "contentProductId": "[variables('analyticRuleObject9')._analyticRulecontentProductId9]", "id": "[variables('analyticRuleObject9')._analyticRulecontentProductId9]", "version": "[variables('analyticRuleObject9').analyticRuleVersion9]" @@ -1657,9 +1639,9 @@ "location": "[parameters('workspace-location')]", "properties": { "description": "Identifies if any tampering is done to either audit log, ATP Safelink, SafeAttachment, AntiPhish, or Dlp policy. \nAn adversary may use this technique to evade detection or avoid other policy-based defenses.\nReferences: https://docs.microsoft.com/powershell/module/exchange/advanced-threat-protection/remove-antiphishrule?view=exchange-ps.", - "displayName": "Office 365 - Office Policy Tampering", + "displayName": "GSA Enriched Office 365 - Office Policy Tampering", "enabled": false, - "query": "let opList = EnrichedMicrosoft365AuditLogs \n | summarize by Operation\n | where Operation has_any (\"Remove\", \"Disable\")\n | where Operation contains \"AntiPhish\" or Operation contains \"SafeAttachment\" or Operation contains \"SafeLinks\" or Operation contains \"Dlp\" or Operation contains \"Audit\"\n | summarize make_set(Operation, 500);\nEnrichedMicrosoft365AuditLogs\n | where RecordType == \"ExchangeAdmin\"\n | where UserType in~ (\"Admin\", \"DcAdmin\")\n | where Operation in~ (opList)\n | extend ClientIPOnly = case( \n ClientIp has \".\", tostring(split(ClientIp, \":\")[0]), \n ClientIp has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIp, \"]\")[0]))),\n ClientIp\n ) \n | extend Port = case(\n ClientIp has \".\", tostring(split(ClientIp, \":\")[1]),\n ClientIp has \"[\", tostring(split(ClientIp, \"]:\")[1]),\n \"\"\n )\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() by Operation, UserType, UserId, ClientIP = ClientIPOnly, Port, ResultStatus, Parameters = tostring(AdditionalProperties.Parameters)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n", + "query": "// Query for EnrichedMicrosoft365AuditLogs\nlet enrichedOpList = EnrichedMicrosoft365AuditLogs \n | summarize by Operation\n | where Operation has_any (\"Remove\", \"Disable\")\n | where Operation contains \"AntiPhish\" \n or Operation contains \"SafeAttachment\" \n or Operation contains \"SafeLinks\" \n or Operation contains \"Dlp\" \n or Operation contains \"Audit\"\n | summarize make_set(Operation, 500);\n\nlet enrichedLogs = EnrichedMicrosoft365AuditLogs\n | where RecordType == \"ExchangeAdmin\"\n | where UserType in~ (\"Admin\", \"DcAdmin\")\n | where Operation in~ (enrichedOpList)\n | extend ClientIPOnly = case( \n ClientIp has \".\", tostring(split(ClientIp, \":\")[0]), \n ClientIp has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIp, \"]\")[0]))),\n ClientIp\n ) \n | extend Port = case(\n ClientIp has \".\", tostring(split(ClientIp, \":\")[1]),\n ClientIp has \"[\", tostring(split(ClientIp, \"]:\")[1]),\n \"\"\n )\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() \n by Operation, UserType, UserId, ClientIP = ClientIPOnly, Port, ResultStatus, Parameters = tostring(AdditionalProperties.Parameters)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Query for OfficeActivity\nlet officeOpList = OfficeActivity \n | summarize by Operation\n | where Operation has_any (\"Remove\", \"Disable\")\n | where Operation contains \"AntiPhish\" \n or Operation contains \"SafeAttachment\" \n or Operation contains \"SafeLinks\" \n or Operation contains \"Dlp\" \n or Operation contains \"Audit\"\n | summarize make_set(Operation, 500);\n\nlet officeLogs = OfficeActivity\n | where RecordType =~ \"ExchangeAdmin\"\n | where UserType in~ (\"Admin\",\"DcAdmin\")\n | where Operation in~ (officeOpList)\n | extend ClientIPOnly = case( \n ClientIP has \".\", tostring(split(ClientIP,\":\")[0]), \n ClientIP has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIP,\"]\")[0]))),\n ClientIP\n ) \n | extend Port = case(\n ClientIP has \".\", tostring(split(ClientIP,\":\")[1]),\n ClientIP has \"[\", tostring(split(ClientIP, \"]:\")[1]),\n \"\"\n )\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() \n by Operation, UserType, UserId, ClientIP = ClientIPOnly, Port, ResultStatus, Parameters\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Combine Enriched Logs and Office Activity Logs\nunion isfuzzy=true enrichedLogs, officeLogs\n| summarize StartTimeUtc = min(StartTimeUtc), EndTimeUtc = max(EndTimeUtc), TotalOperationCount = sum(OperationCount) \n by Operation, UserType, UserId, ClientIP, Port, ResultStatus, Parameters, AccountName, AccountUPNSuffix\n| order by StartTimeUtc desc;\n", "queryFrequency": "P1D", "queryPeriod": "P1D", "severity": "Medium", @@ -1670,10 +1652,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -1686,30 +1668,30 @@ ], "entityMappings": [ { - "entityType": "Account", "fieldMappings": [ { - "identifier": "FullName", - "columnName": "UserId" + "columnName": "UserId", + "identifier": "FullName" }, { - "identifier": "Name", - "columnName": "AccountName" + "columnName": "AccountName", + "identifier": "Name" }, { - "identifier": "UPNSuffix", - "columnName": "AccountUPNSuffix" + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" } - ] + ], + "entityType": "Account" }, { - "entityType": "IP", "fieldMappings": [ { - "identifier": "Address", - "columnName": "ClientIP" + "columnName": "ClientIP", + "identifier": "Address" } - ] + ], + "entityType": "IP" } ] } @@ -1750,7 +1732,7 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('analyticRuleObject10')._analyticRulecontentId10]", "contentKind": "AnalyticsRule", - "displayName": "Office 365 - Office Policy Tampering", + "displayName": "GSA Enriched Office 365 - Office Policy Tampering", "contentProductId": "[variables('analyticRuleObject10')._analyticRulecontentProductId10]", "id": "[variables('analyticRuleObject10')._analyticRulecontentProductId10]", "version": "[variables('analyticRuleObject10').analyticRuleVersion10]" @@ -1780,9 +1762,9 @@ "location": "[parameters('workspace-location')]", "properties": { "description": "Identifies when executable file types are uploaded to Office services such as SharePoint and OneDrive.\nList currently includes exe, inf, gzip, cmd, bat file extensions.\nAdditionally, identifies when a given user is uploading these files to another user's workspace.\nThis may be an indication of a staging location for malware or other malicious activity.", - "displayName": "Office 365 - New Executable via Office FileUploaded Operation", + "displayName": "GSA Enriched Office 365 - New Executable via Office FileUploaded Operation", "enabled": false, - "query": "let threshold = 2;\nlet uploadOp = 'FileUploaded';\nlet execExt = dynamic(['exe', 'inf', 'gzip', 'cmd', 'bat']);\nlet starttime = 8d;\nlet endtime = 1d;\nEnrichedMicrosoft365AuditLogs\n| where TimeGenerated >= ago(endtime)\n| where Operation == uploadOp\n| extend SourceFileExtension = extract(@\"\\.([^\\./]+)$\", 1, tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)) // Extract file extension\n| where SourceFileExtension in (execExt)\n| extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n| extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl)\n| extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n| project TimeGenerated, Id, Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent), Site_Url, SourceRelativeUrl, SourceFileName\n| join kind=leftanti (\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (ago(starttime) .. ago(endtime))\n | where Operation == uploadOp\n | extend SourceFileExtension = extract(@\"\\.([^\\./]+)$\", 1, tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)) // Extract file extension\n | where SourceFileExtension in (execExt)\n | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl)\n | summarize SourceRelativeUrl = make_set(SourceRelativeUrl, 100000), UserId = make_set(UserId, 100000), PrevSeenCount = count() by SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n // Uncomment the line below to enforce the threshold\n // | where PrevSeenCount > threshold\n | mvexpand SourceRelativeUrl, UserId\n | extend SourceRelativeUrl = tostring(SourceRelativeUrl), UserId = tostring(UserId)\n) on SourceFileName, SourceRelativeUrl, UserId\n| extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])\n| extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n| extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n| summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), UserAgents = make_list(UserAgent, 100000), Ids = make_list(Id, 100000), SourceRelativeUrls = make_list(SourceRelativeUrl, 100000), FileNames = make_list(SourceFileName, 100000)\nby Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n| extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n", + "query": "// Set query parameters\nlet threshold = 2;\nlet uploadOp = 'FileUploaded';\n// Extensions that are interesting. Add/Remove to this list as you see fit\nlet execExt = dynamic(['exe', 'inf', 'gzip', 'cmd', 'bat']);\nlet starttime = 8d;\nlet endtime = 1d;\n\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated >= ago(endtime)\n | where Operation =~ uploadOp\n | where SourceFileExtension has_any (execExt)\n | extend RecordType = coalesce(tostring(RecordType), \"UnknownRecordType\") // Ensure RecordType is a string\n | project TimeGenerated, OfficeId, OfficeWorkload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, UserAgent, Site_Url, SourceRelativeUrl, SourceFileName\n | join kind=leftanti (\n OfficeActivity\n | where TimeGenerated between (ago(starttime) .. ago(endtime))\n | where Operation =~ uploadOp\n | where SourceFileExtension has_any (execExt)\n | summarize SourceRelativeUrl = make_set(SourceRelativeUrl, 100000), UserId = make_set(UserId, 100000), PrevSeenCount = count() by SourceFileName\n // Uncomment the line below to enforce the threshold\n //| where PrevSeenCount > threshold\n | mvexpand SourceRelativeUrl, UserId\n | extend SourceRelativeUrl = tostring(SourceRelativeUrl), UserId = tostring(UserId) // Ensure consistent types for SourceRelativeUrl and UserId\n ) on SourceFileName, SourceRelativeUrl, UserId\n | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])\n | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), UserAgents = make_list(UserAgent, 100000), OfficeIds = make_list(OfficeId, 100000), SourceRelativeUrls = make_list(SourceRelativeUrl, 100000), FileNames = make_list(tostring(SourceFileName), 100000) // Cast FileNames to string\n by OfficeWorkload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated >= ago(endtime)\n | where Operation == uploadOp\n | extend SourceFileExtension = extract(@\"\\.([^\\./]+)$\", 1, tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)) // Extract file extension\n | where SourceFileExtension in (execExt)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl)\n | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | extend RecordType = coalesce(tostring(RecordType), \"UnknownRecordType\") // Ensure RecordType is a string\n | project TimeGenerated, Id, Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent), Site_Url, SourceRelativeUrl, SourceFileName\n | join kind=leftanti (\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (ago(starttime) .. ago(endtime))\n | where Operation == uploadOp\n | extend SourceFileExtension = extract(@\"\\.([^\\./]+)$\", 1, tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)) // Extract file extension\n | where SourceFileExtension in (execExt)\n | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl)\n | summarize SourceRelativeUrl = make_set(SourceRelativeUrl, 100000), UserId = make_set(UserId, 100000), PrevSeenCount = count() by SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n // Uncomment the line below to enforce the threshold\n //| where PrevSeenCount > threshold\n | mvexpand SourceRelativeUrl, UserId\n | extend SourceRelativeUrl = tostring(SourceRelativeUrl), UserId = tostring(UserId) // Ensure consistent types for SourceRelativeUrl and UserId\n ) on SourceFileName, SourceRelativeUrl, UserId\n | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])\n | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), UserAgents = make_list(UserAgent, 100000), Ids = make_list(Id, 100000), SourceRelativeUrls = make_list(tostring(SourceRelativeUrl), 100000), FileNames = make_list(tostring(SourceFileName), 100000) // Cast FileNames to string\n by Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Combine Office and Enriched Logs\nlet CombinedEvents = EnrichedEvents\n | union isfuzzy=true OfficeEvents\n | summarize arg_min(StartTime, *) by tostring(FileNames), UserId, Site_Url, tostring(RecordType) // Ensure FileNames and RecordType are strings\n | order by StartTime desc;\n\n// Final Output\nCombinedEvents\n | project StartTime, EndTime, Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder, FileNames, UserAgents, AccountName, AccountUPNSuffix;\n", "queryFrequency": "P1D", "queryPeriod": "P8D", "severity": "Low", @@ -1793,10 +1775,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -1809,48 +1791,48 @@ ], "entityMappings": [ { - "entityType": "Account", "fieldMappings": [ { - "identifier": "FullName", - "columnName": "UserId" + "columnName": "UserId", + "identifier": "FullName" }, { - "identifier": "Name", - "columnName": "AccountName" + "columnName": "AccountName", + "identifier": "Name" }, { - "identifier": "UPNSuffix", - "columnName": "AccountUPNSuffix" + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" } - ] + ], + "entityType": "Account" }, { - "entityType": "IP", "fieldMappings": [ { - "identifier": "Address", - "columnName": "ClientIp" + "columnName": "ClientIP", + "identifier": "Address" } - ] + ], + "entityType": "IP" }, { - "entityType": "URL", "fieldMappings": [ { - "identifier": "Url", - "columnName": "Site_Url" + "columnName": "Site_Url", + "identifier": "Url" } - ] + ], + "entityType": "URL" }, { - "entityType": "File", "fieldMappings": [ { - "identifier": "Name", - "columnName": "FileNames" + "columnName": "FileNames", + "identifier": "Name" } - ] + ], + "entityType": "File" } ] } @@ -1891,7 +1873,7 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('analyticRuleObject11')._analyticRulecontentId11]", "contentKind": "AnalyticsRule", - "displayName": "Office 365 - New Executable via Office FileUploaded Operation", + "displayName": "GSA Enriched Office 365 - New Executable via Office FileUploaded Operation", "contentProductId": "[variables('analyticRuleObject11')._analyticRulecontentProductId11]", "id": "[variables('analyticRuleObject11')._analyticRulecontentProductId11]", "version": "[variables('analyticRuleObject11').analyticRuleVersion11]" @@ -1921,9 +1903,9 @@ "location": "[parameters('workspace-location')]", "properties": { "description": "Identifies Office operations that are typically rare and can provide capabilities useful to attackers.", - "displayName": "Office 365 - Rare and Potentially High-Risk Office Operations", + "displayName": "GSA Enriched Office 365 - Rare and Potentially High-Risk Office Operations", "enabled": false, - "query": "EnrichedMicrosoft365AuditLogs\n| where Operation in~ ( \"Add-MailboxPermission\", \"Add-MailboxFolderPermission\", \"Set-Mailbox\", \"New-ManagementRoleAssignment\", \"New-InboxRule\", \"Set-InboxRule\", \"Set-TransportRule\")\nand not(UserId has_any ('NT AUTHORITY\\\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\\\SYSTEM (Microsoft.Exchange.AdminApi.NetCore)', 'NT AUTHORITY\\\\SYSTEM (w3wp)', 'devilfish-applicationaccount') and Operation in~ ( \"Add-MailboxPermission\", \"Set-Mailbox\"))\n| extend ClientIPOnly = tostring(extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?', dynamic([\"IPAddress\"]), ClientIp)[0])\n| extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n", + "query": "// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where Operation in~ ( \"Add-MailboxPermission\", \"Add-MailboxFolderPermission\", \"Set-Mailbox\", \"New-ManagementRoleAssignment\", \"New-InboxRule\", \"Set-InboxRule\", \"Set-TransportRule\")\n and not(UserId has_any ('NT AUTHORITY\\\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\\\SYSTEM (Microsoft.Exchange.AdminApi.NetCore)', 'NT AUTHORITY\\\\SYSTEM (w3wp)', 'devilfish-applicationaccount') \n and Operation in~ ( \"Add-MailboxPermission\", \"Set-Mailbox\"))\n | extend ClientIPOnly = tostring(extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?', dynamic([\"IPAddress\"]), ClientIP)[0])\n | extend AccountName = tostring(split(UserId, \"@\")[0]), \n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Operation in~ ( \"Add-MailboxPermission\", \"Add-MailboxFolderPermission\", \"Set-Mailbox\", \"New-ManagementRoleAssignment\", \"New-InboxRule\", \"Set-InboxRule\", \"Set-TransportRule\")\n and not(UserId has_any ('NT AUTHORITY\\\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\\\SYSTEM (Microsoft.Exchange.AdminApi.NetCore)', 'NT AUTHORITY\\\\SYSTEM (w3wp)', 'devilfish-applicationaccount') \n and Operation in~ ( \"Add-MailboxPermission\", \"Set-Mailbox\"))\n | extend ClientIPOnly = tostring(extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?', dynamic([\"IPAddress\"]), ClientIp)[0])\n | extend AccountName = tostring(split(UserId, \"@\")[0]), \n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Combine and Deduplicate Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeGenerated, *) by Operation, UserId, ClientIPOnly;\n\n// Final Output\nCombinedEvents\n | project TimeGenerated, Operation, UserId, AccountName, AccountUPNSuffix, ClientIPOnly\n", "queryFrequency": "P1D", "queryPeriod": "P1D", "severity": "Low", @@ -1934,10 +1916,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -1950,39 +1932,30 @@ ], "entityMappings": [ { - "entityType": "Account", "fieldMappings": [ { - "identifier": "FullName", - "columnName": "UserId" + "columnName": "UserId", + "identifier": "FullName" }, { - "identifier": "Name", - "columnName": "AccountName" + "columnName": "AccountName", + "identifier": "Name" }, { - "identifier": "UPNSuffix", - "columnName": "AccountUPNSuffix" + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" } - ] - }, - { - "entityType": "IP", - "fieldMappings": [ - { - "identifier": "Address", - "columnName": "ClientIPOnly" - } - ] + ], + "entityType": "Account" }, { - "entityType": "CloudApplication", "fieldMappings": [ { - "identifier": "AppId", - "columnName": "AppId" + "columnName": "ClientIPOnly", + "identifier": "Address" } - ] + ], + "entityType": "IP" } ] } @@ -2023,7 +1996,7 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('analyticRuleObject12')._analyticRulecontentId12]", "contentKind": "AnalyticsRule", - "displayName": "Office 365 - Rare and Potentially High-Risk Office Operations", + "displayName": "GSA Enriched Office 365 - Rare and Potentially High-Risk Office Operations", "contentProductId": "[variables('analyticRuleObject12')._analyticRulecontentProductId12]", "id": "[variables('analyticRuleObject12')._analyticRulecontentProductId12]", "version": "[variables('analyticRuleObject12').analyticRuleVersion12]" @@ -2053,9 +2026,9 @@ "location": "[parameters('workspace-location')]", "properties": { "description": "Identifies anomalies using user behavior by setting a threshold for significant changes in file upload/download activities from new IP addresses. It establishes a baseline of typical behavior, compares it to recent activity, and flags deviations exceeding a default threshold of 25.", - "displayName": "Office 365 - SharePoint File Operation via Previously Unseen IPs", + "displayName": "GSA Enriched Office 365 - SharePoint File Operation via Previously Unseen IPs", "enabled": false, - "query": "let threshold = 0.25;\nlet szSharePointFileOperation = \"SharePointFileOperation\";\nlet szOperations = dynamic([\"FileDownloaded\", \"FileUploaded\"]);\nlet starttime = 14d;\nlet endtime = 1d;\n// Define a baseline of normal user behavior\nlet userBaseline = EnrichedMicrosoft365AuditLogs\n| where TimeGenerated between(ago(starttime)..ago(endtime))\n| where RecordType == szSharePointFileOperation\n| where Operation in (szOperations)\n| extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n| extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n| where isnotempty(UserAgent)\n| summarize Count = count() by UserId, Operation, Site_Url, ClientIp\n| summarize AvgCount = avg(Count) by UserId, Operation, Site_Url, ClientIp;\n// Get recent user activity\nlet recentUserActivity = EnrichedMicrosoft365AuditLogs\n| where TimeGenerated > ago(endtime)\n| where RecordType == szSharePointFileOperation\n| where Operation in (szOperations)\n| extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n| extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n| where isnotempty(UserAgent)\n| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), RecentCount = count() by UserId, UserType, Operation, Site_Url, ClientIp, ObjectId, Workload, UserAgent;\n// Join the baseline and recent activity, and calculate the deviation\nlet UserBehaviorAnalysis = userBaseline \n| join kind=inner (recentUserActivity) on UserId, Operation, Site_Url, ClientIp\n| extend Deviation = abs(RecentCount - AvgCount) / AvgCount;\n// Filter for significant deviations\nUserBehaviorAnalysis\n| where Deviation > threshold\n| project StartTimeUtc, EndTimeUtc, UserId, UserType, Operation, ClientIp, Site_Url, ObjectId, Workload, UserAgent, Deviation, Count=RecentCount\n| order by Count desc, ClientIp asc, Operation asc, UserId asc\n| extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n", + "query": "let threshold = 25;\nlet szSharePointFileOperation = \"SharePointFileOperation\";\nlet szOperations = dynamic([\"FileDownloaded\", \"FileUploaded\"]);\nlet starttime = 14d;\nlet endtime = 1d;\n\n// Define a baseline of normal user behavior for OfficeActivity\nlet userBaselineOffice = OfficeActivity\n | where TimeGenerated between(ago(starttime)..ago(endtime))\n | where RecordType =~ szSharePointFileOperation\n | where Operation in~ (szOperations)\n | where isnotempty(UserAgent)\n | summarize Count = count() by UserId, Operation, Site_Url, ClientIP\n | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url, ClientIP;\n\n// Get recent user activity for OfficeActivity\nlet recentUserActivityOffice = OfficeActivity\n | where TimeGenerated > ago(endtime)\n | where RecordType =~ szSharePointFileOperation\n | where Operation in~ (szOperations)\n | where isnotempty(UserAgent)\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), RecentCount = count() by UserId, UserType, Operation, Site_Url, ClientIP, OfficeObjectId, OfficeWorkload, UserAgent;\n\n// Define a baseline of normal user behavior for EnrichedMicrosoft365AuditLogs\nlet userBaselineEnriched = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between(ago(starttime)..ago(endtime))\n | where RecordType == szSharePointFileOperation\n | where Operation in (szOperations)\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(UserAgent)\n | summarize Count = count() by UserId, Operation, Site_Url, ClientIp\n | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url, ClientIp;\n\n// Get recent user activity for EnrichedMicrosoft365AuditLogs\nlet recentUserActivityEnriched = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated > ago(endtime)\n | where RecordType == szSharePointFileOperation\n | where Operation in (szOperations)\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(UserAgent)\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), RecentCount = count() by UserId, UserType, Operation, Site_Url, ClientIp, ObjectId, Workload, UserAgent;\n\n// Combine user baselines and recent activity, calculate deviation, and deduplicate\nlet UserBehaviorAnalysis = userBaselineOffice\n | join kind=inner (recentUserActivityOffice) on UserId, Operation, Site_Url, ClientIP\n | union (userBaselineEnriched | join kind=inner (recentUserActivityEnriched) on UserId, Operation, Site_Url, ClientIp)\n | extend Deviation = abs(RecentCount - AvgCount) / AvgCount;\n\n// Filter for significant deviations\nUserBehaviorAnalysis\n | where Deviation > threshold\n | project StartTimeUtc, EndTimeUtc, UserId, UserType, Operation, ClientIP, Site_Url, ObjectId, OfficeObjectId, OfficeWorkload, Workload, UserAgent, Deviation, Count = RecentCount\n | order by Count desc, ClientIP asc, Operation asc, UserId asc\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n", "queryFrequency": "P1D", "queryPeriod": "P14D", "severity": "Medium", @@ -2066,10 +2039,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -2080,39 +2053,39 @@ ], "entityMappings": [ { - "entityType": "Account", "fieldMappings": [ { - "identifier": "FullName", - "columnName": "UserId" + "columnName": "UserId", + "identifier": "FullName" }, { - "identifier": "Name", - "columnName": "AccountName" + "columnName": "AccountName", + "identifier": "Name" }, { - "identifier": "UPNSuffix", - "columnName": "AccountUPNSuffix" + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" } - ] + ], + "entityType": "Account" }, { - "entityType": "IP", "fieldMappings": [ { - "identifier": "Address", - "columnName": "ClientIp" + "columnName": "ClientIP", + "identifier": "Address" } - ] + ], + "entityType": "IP" }, { - "entityType": "URL", "fieldMappings": [ { - "identifier": "Url", - "columnName": "Site_Url" + "columnName": "Site_Url", + "identifier": "Url" } - ] + ], + "entityType": "URL" } ] } @@ -2153,7 +2126,7 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('analyticRuleObject13')._analyticRulecontentId13]", "contentKind": "AnalyticsRule", - "displayName": "Office 365 - SharePoint File Operation via Previously Unseen IPs", + "displayName": "GSA Enriched Office 365 - SharePoint File Operation via Previously Unseen IPs", "contentProductId": "[variables('analyticRuleObject13')._analyticRulecontentProductId13]", "id": "[variables('analyticRuleObject13')._analyticRulecontentProductId13]", "version": "[variables('analyticRuleObject13').analyticRuleVersion13]" @@ -2183,9 +2156,9 @@ "location": "[parameters('workspace-location')]", "properties": { "description": "Identifies anomalies if the number of documents uploaded or downloaded from device(s) associated with a previously unseen user agent exceeds a threshold (default is 5) and deviation (default is 25%).", - "displayName": "Office 365 - SharePointFileOperation via devices with previously unseen user agents", + "displayName": "GSA Enriched Office 365 - SharePointFileOperation via devices with previously unseen user agents", "enabled": false, - "query": "// Set threshold for the number of downloads/uploads from a new user agent\nlet threshold = 5;\n// Define constants for SharePoint file operations\nlet szSharePointFileOperation = \"SharePointFileOperation\";\nlet szOperations = dynamic([\"FileDownloaded\", \"FileUploaded\"]);\n// Define the historical activity for analysis\nlet starttime = 14d; // Define the start time for historical data (14 days ago)\nlet endtime = 1d; // Define the end time for historical data (1 day ago)\n// Extract the base events for analysis\nlet Baseevents =\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (ago(starttime) .. ago(endtime))\n | where RecordType == szSharePointFileOperation\n | where Operation in (szOperations)\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(UserAgent);\n// Identify frequently occurring user agents\nlet FrequentUA = Baseevents\n | summarize FUACount = count() by UserAgent, RecordType, Operation\n | where FUACount >= threshold\n | distinct UserAgent;\n// Calculate a user baseline for further analysis\nlet UserBaseLine = Baseevents\n | summarize Count = count() by UserId, Operation, Site_Url\n | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url;\n// Extract recent activity for analysis\nlet RecentActivity = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated > ago(endtime)\n | where RecordType == szSharePointFileOperation\n | where Operation in (szOperations)\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(UserAgent)\n | where UserAgent in (FrequentUA)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), ObjectIdCount = dcount(ObjectId), ObjectIdList = make_set(ObjectId), UserAgentSeenCount = count()\n by RecordType, Operation, UserAgent, UserId, ClientIp, Site_Url;\n// Analyze user behavior based on baseline and recent activity\nlet UserBehaviorAnalysis = UserBaseLine\n | join kind=inner (RecentActivity) on UserId, Operation, Site_Url\n | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount;\n// Filter and format results for specific user behavior analysis\nUserBehaviorAnalysis\n | where Deviation > 0.25\n | extend UserIdName = tostring(split(UserId, '@')[0]), UserIdUPNSuffix = tostring(split(UserId, '@')[1])\n | project-reorder StartTime, EndTime, UserAgent, UserAgentSeenCount, UserId, ClientIp, Site_Url\n | order by UserAgentSeenCount desc, UserAgent asc, UserId asc, Site_Url asc\n", + "query": "let threshold = 5;\nlet szSharePointFileOperation = \"SharePointFileOperation\";\nlet szOperations = dynamic([\"FileDownloaded\", \"FileUploaded\"]);\nlet starttime = 14d;\nlet endtime = 1d;\n\n// OfficeActivity - Base Events\nlet BaseeventsOffice = OfficeActivity\n | where TimeGenerated between (ago(starttime)..ago(endtime))\n | where RecordType =~ szSharePointFileOperation\n | where Operation in~ (szOperations)\n | where isnotempty(UserAgent);\n\n// OfficeActivity - Frequent User Agents\nlet FrequentUAOffice = BaseeventsOffice\n | summarize FUACount = count() by UserAgent, RecordType, Operation\n | where FUACount >= threshold\n | distinct UserAgent;\n\n// OfficeActivity - User Baseline\nlet UserBaseLineOffice = BaseeventsOffice\n | summarize Count = count() by UserId, Operation, Site_Url\n | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url;\n\n// OfficeActivity - Recent User Activity\nlet RecentActivityOffice = OfficeActivity\n | where TimeGenerated > ago(endtime)\n | where RecordType =~ szSharePointFileOperation\n | where Operation in~ (szOperations)\n | where isnotempty(UserAgent)\n | where UserAgent in~ (FrequentUAOffice)\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OfficeObjectIdCount = dcount(OfficeObjectId), OfficeObjectIdList = make_set(OfficeObjectId), UserAgentSeenCount = count()\n by RecordType, Operation, UserAgent, UserType, UserId, ClientIP , OfficeWorkload, Site_Url;\n\n// EnrichedMicrosoft365AuditLogs - Base Events\nlet BaseeventsEnriched = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (ago(starttime)..ago(endtime))\n | where RecordType == szSharePointFileOperation\n | where Operation in (szOperations)\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(UserAgent);\n\n// EnrichedMicrosoft365AuditLogs - Frequent User Agents\nlet FrequentUAEnriched = BaseeventsEnriched\n | summarize FUACount = count() by UserAgent, RecordType, Operation\n | where FUACount >= threshold\n | distinct UserAgent;\n\n// EnrichedMicrosoft365AuditLogs - User Baseline\nlet UserBaseLineEnriched = BaseeventsEnriched\n | summarize Count = count() by UserId, Operation, Site_Url\n | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url;\n\n// EnrichedMicrosoft365AuditLogs - Recent User Activity\nlet RecentActivityEnriched = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated > ago(endtime)\n | where RecordType == szSharePointFileOperation\n | where Operation in (szOperations)\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | extend ClientIP = ClientIp\n | where isnotempty(UserAgent)\n | where UserAgent in (FrequentUAEnriched)\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ObjectIdCount = dcount(ObjectId), ObjectIdList = make_set(ObjectId), UserAgentSeenCount = count()\n by RecordType, Operation, UserAgent, UserId,ClientIP, Site_Url;\n\n// Combine Baseline and Recent Activity, Calculate Deviation, and Deduplicate\nlet UserBehaviorAnalysisOffice = UserBaseLineOffice\n | join kind=inner (RecentActivityOffice) on UserId, Operation, Site_Url\n | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount;\n\nlet UserBehaviorAnalysisEnriched = UserBaseLineEnriched\n | join kind=inner (RecentActivityEnriched) on UserId, Operation, Site_Url\n | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount;\n\n// Combine Office and Enriched Logs\nlet CombinedUserBehaviorAnalysis = UserBehaviorAnalysisOffice\n | union UserBehaviorAnalysisEnriched;\n\n// Filter and Format Final Results\nCombinedUserBehaviorAnalysis\n | where Deviation > 0.25\n | extend UserIdName = tostring(split(UserId, '@')[0]), \n UserIdUPNSuffix = tostring(split(UserId, '@')[1])\n | project-reorder StartTimeUtc, EndTimeUtc, UserAgent, UserAgentSeenCount, UserId, ClientIP, Site_Url\n | order by UserAgentSeenCount desc, UserAgent asc, UserId asc, Site_Url asc\n", "queryFrequency": "P1D", "queryPeriod": "P14D", "severity": "Medium", @@ -2196,10 +2169,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -2210,39 +2183,39 @@ ], "entityMappings": [ { - "entityType": "Account", "fieldMappings": [ { - "identifier": "FullName", - "columnName": "UserId" + "columnName": "UserId", + "identifier": "FullName" }, { - "identifier": "Name", - "columnName": "UserIdName" + "columnName": "UserIdName", + "identifier": "Name" }, { - "identifier": "UPNSuffix", - "columnName": "UserIdUPNSuffix" + "columnName": "UserIdUPNSuffix", + "identifier": "UPNSuffix" } - ] + ], + "entityType": "Account" }, { - "entityType": "IP", "fieldMappings": [ { - "identifier": "Address", - "columnName": "ClientIp" + "columnName": "ClientIP", + "identifier": "Address" } - ] + ], + "entityType": "IP" }, { - "entityType": "URL", "fieldMappings": [ { - "identifier": "Url", - "columnName": "Site_Url" + "columnName": "Site_Url", + "identifier": "Url" } - ] + ], + "entityType": "URL" } ] } @@ -2283,7 +2256,7 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('analyticRuleObject14')._analyticRulecontentId14]", "contentKind": "AnalyticsRule", - "displayName": "Office 365 - SharePointFileOperation via devices with previously unseen user agents", + "displayName": "GSA Enriched Office 365 - SharePointFileOperation via devices with previously unseen user agents", "contentProductId": "[variables('analyticRuleObject14')._analyticRulecontentProductId14]", "id": "[variables('analyticRuleObject14')._analyticRulecontentProductId14]", "version": "[variables('analyticRuleObject14').analyticRuleVersion14]" @@ -2313,9 +2286,9 @@ "location": "[parameters('workspace-location')]", "properties": { "description": "Identifies Office365 Sharepoint File Transfers above a certain threshold in a 15-minute time period.\nPlease note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur.", - "displayName": "Office 365 - Sharepoint File Transfer Above Threshold", + "displayName": "GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold", "enabled": false, - "query": "let threshold = 5000;\nEnrichedMicrosoft365AuditLogs\n| where Workload has_any(\"SharePoint\", \"OneDrive\") and Operation has_any(\"FileDownloaded\", \"FileSyncDownloadedFull\", \"FileSyncUploadedFull\", \"FileUploaded\")\n| summarize count_distinct_ObjectId=dcount(ObjectId), fileslist=make_set(ObjectId, 10000) by UserId, ClientIp, bin(TimeGenerated, 15m)\n| where count_distinct_ObjectId >= threshold\n| extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat(\"SeeFilesListField\",\"_\", tostring(hash(tostring(fileslist)))))\n| extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n", + "query": "let threshold = 5000;\n\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload has_any(\"SharePoint\", \"OneDrive\") \n | where Operation has_any(\"FileDownloaded\", \"FileSyncDownloadedFull\", \"FileSyncUploadedFull\", \"FileUploaded\")\n | extend ClientIP = ClientIp\n | summarize count_distinct_ObjectId = dcount(ObjectId), fileslist = make_set(ObjectId, 10000) \n by UserId, ClientIP, bin(TimeGenerated, 15m)\n | where count_distinct_ObjectId >= threshold\n | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat(\"SeeFilesListField\", \"_\", tostring(hash(tostring(fileslist)))))\n | extend AccountName = tostring(split(UserId, \"@\")[0]), \n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where EventSource == \"SharePoint\" \n | where OfficeWorkload has_any(\"SharePoint\", \"OneDrive\") \n | where Operation has_any(\"FileDownloaded\", \"FileSyncDownloadedFull\", \"FileSyncUploadedFull\", \"FileUploaded\")\n | summarize count_distinct_OfficeObjectId = dcount(OfficeObjectId), fileslist = make_set(OfficeObjectId, 10000) \n by UserId, ClientIP, bin(TimeGenerated, 15m)\n | where count_distinct_OfficeObjectId >= threshold\n | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat(\"SeeFilesListField\", \"_\", tostring(hash(tostring(fileslist)))))\n | extend AccountName = tostring(split(UserId, \"@\")[0]), \n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeGenerated, *) by UserId, ClientIP;\n\n// Final Output\nCombinedEvents\n | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist\n | order by TimeGenerated desc\n", "queryFrequency": "PT15M", "queryPeriod": "PT15M", "severity": "Medium", @@ -2326,10 +2299,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -2340,55 +2313,57 @@ ], "entityMappings": [ { - "entityType": "Account", "fieldMappings": [ { - "identifier": "FullName", - "columnName": "UserId" + "columnName": "UserId", + "identifier": "FullName" }, { - "identifier": "Name", - "columnName": "AccountName" + "columnName": "AccountName", + "identifier": "Name" }, { - "identifier": "UPNSuffix", - "columnName": "AccountUPNSuffix" + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" } - ] + ], + "entityType": "Account" }, { - "entityType": "IP", "fieldMappings": [ { - "identifier": "Address", - "columnName": "ClientIp" + "columnName": "ClientIP", + "identifier": "Address" } - ] + ], + "entityType": "IP" }, { - "entityType": "File", "fieldMappings": [ { - "identifier": "Name", - "columnName": "FileSample" + "columnName": "FileSample", + "identifier": "Name" } - ] + ], + "entityType": "File" } ], "customDetails": { - "TransferCount": "count_distinct_ObjectId", - "FilesList": "fileslist" + "FilesList": "fileslist", + "TransferCount": "count_distinct_ObjectId" }, "incidentConfiguration": { "createIncident": true, "groupingConfiguration": { - "enabled": true, + "reopenClosedIncident": false, + "groupByAlertDetails": [], + "groupByCustomDetails": [], "lookbackDuration": "5h", "groupByEntities": [ "Account" ], "matchingMethod": "Selected", - "reopenClosedIncident": false + "enabled": true } } } @@ -2429,7 +2404,7 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('analyticRuleObject15')._analyticRulecontentId15]", "contentKind": "AnalyticsRule", - "displayName": "Office 365 - Sharepoint File Transfer Above Threshold", + "displayName": "GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold", "contentProductId": "[variables('analyticRuleObject15')._analyticRulecontentProductId15]", "id": "[variables('analyticRuleObject15')._analyticRulecontentProductId15]", "version": "[variables('analyticRuleObject15').analyticRuleVersion15]" @@ -2458,10 +2433,10 @@ "kind": "Scheduled", "location": "[parameters('workspace-location')]", "properties": { - "description": "Identifies Office365 Sharepoint File Transfers with a distinct folder count above a certain threshold in a 15-minute time period.\nPlease note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur.", - "displayName": "Office 365 - Sharepoint File Transfer Above Threshold", + "description": "Identifies Office365 Sharepoint File Transfers with a distinct folder count above a certain threshold in a 15-minute time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur.", + "displayName": "GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold", "enabled": false, - "query": "let threshold = 500;\nEnrichedMicrosoft365AuditLogs\n| where Workload has_any(\"SharePoint\", \"OneDrive\") and Operation has_any(\"FileDownloaded\", \"FileSyncDownloadedFull\", \"FileSyncUploadedFull\", \"FileUploaded\")\n| extend EventSource = tostring(parse_json(tostring(AdditionalProperties)).EventSource)\n| extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n| summarize count_distinct_ObjectId = dcount(ObjectId), dirlist = make_set(ObjectId, 10000) by UserId, ClientIp, UserAgent, bin(TimeGenerated, 15m)\n| where count_distinct_ObjectId >= threshold\n| extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat(\"SeeDirListField\",\"_\", tostring(hash(tostring(dirlist)))))\n| extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n", + "query": "let threshold = 5000;\n // EnrichedMicrosoft365AuditLogs Query\n let EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload has_any(\"SharePoint\", \"OneDrive\")\n | where Operation has_any(\"FileDownloaded\", \"FileSyncDownloadedFull\", \"FileSyncUploadedFull\", \"FileUploaded\")\n | extend UserAgent = tostring(AdditionalProperties[\"UserAgent\"])\n | summarize count_distinct_ObjectId = dcount(ObjectId), \n fileslist = make_set(ObjectId, 10000), \n any_UserAgent = any(UserAgent)\n by UserId, ClientIP = ClientIp, bin(TimeGenerated, 15m)\n | where count_distinct_ObjectId >= threshold\n | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat(\"SeeFilesListField\", \"_\", tostring(hash(tostring(fileslist)))))\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n // OfficeActivity Query\n let OfficeEvents = OfficeActivity\n | where EventSource == \"SharePoint\"\n | where OfficeWorkload has_any(\"SharePoint\", \"OneDrive\")\n | where Operation has_any(\"FileDownloaded\", \"FileSyncDownloadedFull\", \"FileSyncUploadedFull\", \"FileUploaded\")\n | summarize count_distinct_OfficeObjectId = dcount(OfficeObjectId), \n fileslist = make_set(OfficeObjectId, 10000), \n any_UserAgent = any(UserAgent)\n by UserId, ClientIP, bin(TimeGenerated, 15m)\n | where count_distinct_OfficeObjectId >= threshold\n | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat(\"SeeFilesListField\", \"_\", tostring(hash(tostring(fileslist)))))\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n // Combine Office and Enriched Logs\n let CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeGenerated, *) by UserId, ClientIP, any_UserAgent;\n // Final Output\n CombinedEvents\n | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, UserAgent = any_UserAgent, count_distinct_ObjectId = coalesce(count_distinct_ObjectId, count_distinct_OfficeObjectId), fileslist\n | order by TimeGenerated desc\n", "queryFrequency": "PT15M", "queryPeriod": "PT15M", "severity": "Medium", @@ -2472,10 +2447,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -2486,55 +2461,57 @@ ], "entityMappings": [ { - "entityType": "Account", "fieldMappings": [ { - "identifier": "FullName", - "columnName": "UserId" + "columnName": "UserId", + "identifier": "FullName" }, { - "identifier": "Name", - "columnName": "AccountName" + "columnName": "AccountName", + "identifier": "Name" }, { - "identifier": "UPNSuffix", - "columnName": "AccountUPNSuffix" + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" } - ] + ], + "entityType": "Account" }, { - "entityType": "IP", "fieldMappings": [ { - "identifier": "Address", - "columnName": "ClientIp" + "columnName": "ClientIP", + "identifier": "Address" } - ] + ], + "entityType": "IP" }, { - "entityType": "File", "fieldMappings": [ { - "identifier": "Name", - "columnName": "DirSample" + "columnName": "FileSample", + "identifier": "Name" } - ] + ], + "entityType": "File" } ], "customDetails": { - "TransferCount": "count_distinct_ObjectId", - "FilesList": "dirlist" + "FilesList": "fileslist", + "TransferCount": "count_distinct_ObjectId" }, "incidentConfiguration": { "createIncident": true, "groupingConfiguration": { - "enabled": true, + "reopenClosedIncident": false, + "groupByAlertDetails": [], + "groupByCustomDetails": [], "lookbackDuration": "5h", "groupByEntities": [ "Account" ], "matchingMethod": "Selected", - "reopenClosedIncident": false + "enabled": true } } } @@ -2575,7 +2552,7 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('analyticRuleObject16')._analyticRulecontentId16]", "contentKind": "AnalyticsRule", - "displayName": "Office 365 - Sharepoint File Transfer Above Threshold", + "displayName": "GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold", "contentProductId": "[variables('analyticRuleObject16')._analyticRulecontentProductId16]", "id": "[variables('analyticRuleObject16')._analyticRulecontentProductId16]", "version": "[variables('analyticRuleObject16').analyticRuleVersion16]" @@ -2618,10 +2595,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "NetworkAccessTrafficLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -2631,22 +2608,22 @@ ], "entityMappings": [ { - "entityType": "IP", "fieldMappings": [ { - "identifier": "Address", - "columnName": "SourceIp" + "columnName": "SourceIp", + "identifier": "Address" } - ] + ], + "entityType": "IP" }, { - "entityType": "URL", "fieldMappings": [ { - "identifier": "Url", - "columnName": "DestinationIp" + "columnName": "DestinationIp", + "identifier": "Url" } - ] + ], + "entityType": "URL" } ] } @@ -2730,10 +2707,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -2743,22 +2720,22 @@ ], "entityMappings": [ { - "entityType": "IP", "fieldMappings": [ { - "identifier": "Address", - "columnName": "IPCustomEntity" + "columnName": "IPCustomEntity", + "identifier": "Address" } - ] + ], + "entityType": "IP" }, { - "entityType": "URL", "fieldMappings": [ { - "identifier": "Url", - "columnName": "FqdnCustomEntity" + "columnName": "FqdnCustomEntity", + "identifier": "Url" } - ] + ], + "entityType": "URL" } ] } @@ -2842,10 +2819,10 @@ "status": "Available", "requiredDataConnectors": [ { + "connectorId": "AzureActiveDirectory", "dataTypes": [ "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" + ] } ], "tactics": [ @@ -2856,22 +2833,22 @@ ], "entityMappings": [ { - "entityType": "IP", "fieldMappings": [ { - "identifier": "Address", - "columnName": "SourceIp" + "columnName": "SourceIp", + "identifier": "Address" } - ] + ], + "entityType": "IP" }, { - "entityType": "URL", "fieldMappings": [ { - "identifier": "Url", - "columnName": "Fqdn" + "columnName": "Fqdn", + "identifier": "Url" } - ] + ], + "entityType": "URL" } ] } @@ -2941,9 +2918,9 @@ "location": "[parameters('workspace-location')]", "properties": { "eTag": "*", - "displayName": "Anomalous access to other users' mailboxes", + "displayName": "GSA Enriched Office 365 - Anomalous access to other users' mailboxes", "category": "Hunting Queries", - "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = totimespan((endtime - starttime) * 2);\n// Adjust this value to alter how many mailbox (other than their own) a user needs to access before being included in results\nlet user_threshold = 1;\n// Adjust this value to alter how many mailbox folders in other's email accounts a users needs to access before being included in results.\nlet folder_threshold = 5;\n// Exclude historical as known good (set lookback and timeframe to same value to skip this)\nEnrichedMicrosoft365AuditLogs\n| where TimeGenerated between (ago(lookback)..starttime)\n| where Operation =~ \"MailItemsAccessed\"\n| where ResultStatus =~ \"Succeeded\"\n| extend MailboxOwnerUPN = tostring(parse_json(AdditionalProperties).MailboxOwnerUPN)\n| where tolower(MailboxOwnerUPN) != tolower(UserId)\n| join kind=rightanti (\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime..endtime)\n | where Operation =~ \"MailItemsAccessed\"\n | where ResultStatus =~ \"Succeeded\"\n | extend MailboxOwnerUPN = tostring(parse_json(AdditionalProperties).MailboxOwnerUPN)\n | where tolower(MailboxOwnerUPN) != tolower(UserId)\n) on MailboxOwnerUPN, UserId\n| where isnotempty(tostring(parse_json(AdditionalProperties).Folders))\n| mv-expand Folders = parse_json(AdditionalProperties).Folders\n| extend folders = tostring(Folders.Path)\n| extend ClientIP = iif(ClientIp startswith \"[\", extract(\"\\\\[([^\\\\]]*)\", 1, ClientIp), ClientIp)\n| extend ClientInfoString = tostring(parse_json(AdditionalProperties).ClientInfoString)\n| extend MailboxGuid = tostring(parse_json(AdditionalProperties).MailboxGuid)\n| summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), set_folders = make_set(folders, 100000), set_ClientInfoString = make_set(ClientInfoString, 100000), set_ClientIP = make_set(ClientIP, 100000), set_MailboxGuid = make_set(MailboxGuid, 100000), set_MailboxOwnerUPN = make_set(MailboxOwnerUPN, 100000) by UserId\n| extend folder_count = array_length(set_folders)\n| extend user_count = array_length(set_MailboxGuid)\n| where user_count > user_threshold or folder_count > folder_threshold\n| extend Reason = case(user_count > user_threshold and folder_count > folder_threshold, \"Both User and Folder Threshold Exceeded\", folder_count > folder_threshold and user_count < user_threshold, \"Folder Count Threshold Exceeded\", \"User Threshold Exceeded\")\n| sort by user_count desc\n| project-reorder UserId, user_count, folder_count, set_MailboxOwnerUPN, set_ClientIP, set_ClientInfoString, set_folders\n| extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n| extend Account_0_Name = AccountName\n| extend Account_0_UPNSuffix = AccountUPNSuffix\n", + "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = totimespan((endtime - starttime) * 2);\nlet user_threshold = 1; // Threshold for number of mailboxes accessed\nlet folder_threshold = 5; // Threshold for number of mailbox folders accessed\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated between (ago(lookback)..starttime)\n | where Operation =~ \"MailItemsAccessed\"\n | where ResultStatus =~ \"Succeeded\"\n | where tolower(MailboxOwnerUPN) != tolower(UserId)\n | join kind=rightanti (\n OfficeActivity\n | where TimeGenerated between (starttime..endtime)\n | where Operation =~ \"MailItemsAccessed\"\n | where ResultStatus =~ \"Succeeded\"\n | where tolower(MailboxOwnerUPN) != tolower(UserId)\n ) on MailboxOwnerUPN, UserId\n | where isnotempty(Folders)\n | mv-expand parse_json(Folders)\n | extend folders = tostring(Folders.Path)\n | extend ClientIP = iif(Client_IPAddress startswith \"[\", extract(\"\\\\[([^\\\\]]*)\", 1, Client_IPAddress), Client_IPAddress)\n | summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), set_folders = make_set(folders, 100000), set_ClientInfoString = make_set(ClientInfoString, 100000), set_ClientIP = make_set(ClientIP, 100000), set_MailboxGuid = make_set(MailboxGuid, 100000), set_MailboxOwnerUPN = make_set(MailboxOwnerUPN, 100000) by UserId\n | extend folder_count = array_length(set_folders)\n | extend user_count = array_length(set_MailboxGuid)\n | where user_count > user_threshold or folder_count > folder_threshold\n | extend Reason = case(user_count > user_threshold and folder_count > folder_threshold, \"Both User and Folder Threshold Exceeded\", folder_count > folder_threshold and user_count < user_threshold, \"Folder Count Threshold Exceeded\", \"User Threshold Exceeded\")\n | sort by user_count desc\n | project-reorder UserId, user_count, folder_count, set_MailboxOwnerUPN, set_ClientIP, set_ClientInfoString, set_folders\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (ago(lookback)..starttime)\n | where Operation =~ \"MailItemsAccessed\"\n | where ResultStatus =~ \"Succeeded\"\n | extend MailboxOwnerUPN = tostring(parse_json(AdditionalProperties).MailboxOwnerUPN)\n | where tolower(MailboxOwnerUPN) != tolower(UserId)\n | join kind=rightanti (\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime..endtime)\n | where Operation =~ \"MailItemsAccessed\"\n | where ResultStatus =~ \"Succeeded\"\n | extend MailboxOwnerUPN = tostring(parse_json(AdditionalProperties).MailboxOwnerUPN)\n | where tolower(MailboxOwnerUPN) != tolower(UserId)\n ) on MailboxOwnerUPN, UserId\n | where isnotempty(tostring(parse_json(AdditionalProperties).Folders))\n | mv-expand Folders = parse_json(AdditionalProperties).Folders\n | extend folders = tostring(Folders.Path)\n | extend ClientIP = iif(ClientIp startswith \"[\", extract(\"\\\\[([^\\\\]]*)\", 1, ClientIp), ClientIp)\n | extend ClientInfoString = tostring(parse_json(AdditionalProperties).ClientInfoString)\n | extend MailboxGuid = tostring(parse_json(AdditionalProperties).MailboxGuid)\n | summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), set_folders = make_set(folders, 100000), set_ClientInfoString = make_set(ClientInfoString, 100000), set_ClientIP = make_set(ClientIP, 100000), set_MailboxGuid = make_set(MailboxGuid, 100000), set_MailboxOwnerUPN = make_set(MailboxOwnerUPN, 100000) by UserId\n | extend folder_count = array_length(set_folders)\n | extend user_count = array_length(set_MailboxGuid)\n | where user_count > user_threshold or folder_count > folder_threshold\n | extend Reason = case(user_count > user_threshold and folder_count > folder_threshold, \"Both User and Folder Threshold Exceeded\", folder_count > folder_threshold and user_count < user_threshold, \"Folder Count Threshold Exceeded\", \"User Threshold Exceeded\")\n | sort by user_count desc\n | project-reorder UserId, user_count, folder_count, set_MailboxOwnerUPN, set_ClientIP, set_ClientInfoString, set_folders\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by UserId, ClientIP;\n// Final Output\nCombinedEvents\n | project UserId, user_count, folder_count, set_MailboxOwnerUPN, set_ClientIP, set_ClientInfoString, set_folders, AccountName, AccountUPNSuffix\n | order by user_count desc\n", "version": 2, "tags": [ { @@ -2997,10 +2974,10 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('huntingQueryObject1')._huntingQuerycontentId1]", "contentKind": "HuntingQuery", - "displayName": "Anomalous access to other users' mailboxes", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject1')._huntingQuerycontentId1,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject1')._huntingQuerycontentId1,'-', '2.0.1')))]", - "version": "2.0.1" + "displayName": "GSA Enriched Office 365 - Anomalous access to other users' mailboxes", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject1')._huntingQuerycontentId1,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject1')._huntingQuerycontentId1,'-', '2.0.2')))]", + "version": "2.0.2" } }, { @@ -3111,9 +3088,9 @@ "location": "[parameters('workspace-location')]", "properties": { "eTag": "*", - "displayName": "External user from a new organisation added to Teams", + "displayName": "GSA Enriched Office 365 - External user from a new organisation added to Teams", "category": "Hunting Queries", - "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = totimespan((endtime - starttime) * 7);\nlet known_orgs = (\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (ago(lookback) .. starttime)\n | where Workload == \"MicrosoftTeams\"\n | where Operation in (\"MemberAdded\", \"TeamsSessionStarted\")\n // Extract the correct UPN and parse our external organization domain\n | extend Members = parse_json(tostring(AdditionalProperties.Members))\n | extend UPN = iif(Operation == \"MemberAdded\", tostring(Members[0].UPN), UserId)\n | extend Organization = tostring(split(split(UPN, \"_\")[1], \"#\")[0])\n | where isnotempty(Organization)\n | summarize by Organization\n);\nEnrichedMicrosoft365AuditLogs\n| where TimeGenerated between (starttime .. endtime)\n| where Workload == \"MicrosoftTeams\"\n| where Operation == \"MemberAdded\"\n| extend Members = parse_json(tostring(AdditionalProperties.Members))\n| extend UPN = tostring(Members[0].UPN)\n| extend Organization = tostring(split(split(UPN, \"_\")[1], \"#\")[0])\n| where isnotempty(Organization)\n| where Organization !in (known_orgs)\n| extend AccountName = tostring(split(UPN, \"@\")[0]), AccountUPNSuffix = tostring(split(UPN, \"@\")[1])\n| extend Account_0_Name = AccountName\n| extend Account_0_UPNSuffix = AccountUPNSuffix\n", + "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = totimespan((endtime - starttime) * 7);\n// OfficeActivity Known Organizations\nlet known_orgs_office = (\n OfficeActivity\n | where TimeGenerated between(ago(lookback)..starttime)\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberAdded\" or Operation =~ \"TeamsSessionStarted\"\n | extend UPN = iif(Operation == \"MemberAdded\", tostring(Members[0].UPN), UserId)\n | extend Organization = tostring(split(split(UPN, \"_\")[1], \"#\")[0])\n | where isnotempty(Organization)\n | summarize by Organization\n);\n// OfficeActivity Query for New Organizations\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated between(starttime..endtime)\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberAdded\"\n | extend UPN = tostring(parse_json(Members)[0].UPN)\n | extend Organization = tostring(split(split(UPN, \"_\")[1], \"#\")[0])\n | where isnotempty(Organization)\n | where Organization !in (known_orgs_office)\n | extend AccountName = tostring(split(UPN, \"@\")[0]), AccountUPNSuffix = tostring(split(UPN, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n// EnrichedMicrosoft365AuditLogs Known Organizations\nlet known_orgs_enriched = (\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between(ago(lookback)..starttime)\n | where Workload == \"MicrosoftTeams\"\n | where Operation in (\"MemberAdded\", \"TeamsSessionStarted\")\n | extend Members = parse_json(tostring(AdditionalProperties.Members))\n | extend UPN = iif(Operation == \"MemberAdded\", tostring(Members[0].UPN), UserId)\n | extend Organization = tostring(split(split(UPN, \"_\")[1], \"#\")[0])\n | where isnotempty(Organization)\n | summarize by Organization\n);\n// EnrichedMicrosoft365AuditLogs Query for New Organizations\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between(starttime..endtime)\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"MemberAdded\"\n | extend Members = parse_json(tostring(AdditionalProperties.Members))\n | extend UPN = tostring(Members[0].UPN)\n | extend Organization = tostring(split(split(UPN, \"_\")[1], \"#\")[0])\n | where isnotempty(Organization)\n | where Organization !in (known_orgs_enriched)\n | extend AccountName = tostring(split(UPN, \"@\")[0]), AccountUPNSuffix = tostring(split(UPN, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeGenerated, *) by Organization, UPN;\n// Final Output\nCombinedEvents\n | project Organization, UPN, AccountName, AccountUPNSuffix\n | order by Organization asc\n", "version": 2, "tags": [ { @@ -3167,10 +3144,10 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('huntingQueryObject3')._huntingQuerycontentId3]", "contentKind": "HuntingQuery", - "displayName": "External user from a new organisation added to Teams", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject3')._huntingQuerycontentId3,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject3')._huntingQuerycontentId3,'-', '2.0.1')))]", - "version": "2.0.1" + "displayName": "GSA Enriched Office 365 - External user from a new organisation added to Teams", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject3')._huntingQuerycontentId3,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject3')._huntingQuerycontentId3,'-', '2.0.2')))]", + "version": "2.0.2" } }, { @@ -3196,9 +3173,9 @@ "location": "[parameters('workspace-location')]", "properties": { "eTag": "*", - "displayName": "Mail Redirect via ExO Transport Rule", + "displayName": "GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule", "category": "Hunting Queries", - "query": "EnrichedMicrosoft365AuditLogs\n| where Workload == \"Exchange\"\n| where Operation in (\"New-TransportRule\", \"Set-TransportRule\")\n| mv-apply DynamicParameters = todynamic(AdditionalProperties.Parameters) on (summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)))\n| extend RuleName = case(\n Operation == \"Set-TransportRule\", ObjectId,\n Operation == \"New-TransportRule\", ParsedParameters.Name,\n \"Unknown\")\n| mv-expand ExpandedParameters = todynamic(AdditionalProperties.Parameters)\n| where ExpandedParameters.Name in (\"BlindCopyTo\", \"RedirectMessageTo\") and isnotempty(ExpandedParameters.Value)\n| extend RedirectTo = ExpandedParameters.Value\n| extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIp)[0]\n| project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, Operation, RuleName, AdditionalProperties\n| extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n", + "query": "// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload == \"Exchange\"\n | where Operation in (\"New-TransportRule\", \"Set-TransportRule\")\n | mv-apply DynamicParameters = todynamic(AdditionalProperties.Parameters) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | extend RuleName = case(\n Operation == \"Set-TransportRule\", ObjectId,\n Operation == \"New-TransportRule\", ParsedParameters.Name,\n \"Unknown\"\n )\n | mv-expand ExpandedParameters = todynamic(AdditionalProperties.Parameters)\n | where ExpandedParameters.Name in (\"BlindCopyTo\", \"RedirectMessageTo\") and isnotempty(ExpandedParameters.Value)\n | extend RedirectTo = ExpandedParameters.Value\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIp)[0]\n | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, Operation, RuleName, AdditionalProperties\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload == \"Exchange\"\n | where Operation in (\"New-TransportRule\", \"Set-TransportRule\")\n | mv-apply DynamicParameters = todynamic(Parameters) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | extend RuleName = case(\n Operation == \"Set-TransportRule\", OfficeObjectId,\n Operation == \"New-TransportRule\", ParsedParameters.Name,\n \"Unknown\"\n )\n | mv-expand ExpandedParameters = todynamic(Parameters)\n | where ExpandedParameters.Name in (\"BlindCopyTo\", \"RedirectMessageTo\") and isnotempty(ExpandedParameters.Value)\n | extend RedirectTo = ExpandedParameters.Value\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIP)[0]\n | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, Operation, RuleName, Parameters\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, IP_0_Address = IPAddress;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeGenerated, *) by RuleName, UserId;\n// Final Output\nCombinedEvents\n | project TimeGenerated, RuleName, RedirectTo, IPAddress, Port, UserId, AccountName, AccountUPNSuffix\n | order by TimeGenerated desc\n", "version": 2, "tags": [ { @@ -3252,10 +3229,10 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('huntingQueryObject4')._huntingQuerycontentId4]", "contentKind": "HuntingQuery", - "displayName": "Mail Redirect via ExO Transport Rule", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject4')._huntingQuerycontentId4,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject4')._huntingQuerycontentId4,'-', '2.0.1')))]", - "version": "2.0.1" + "displayName": "GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject4')._huntingQuerycontentId4,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject4')._huntingQuerycontentId4,'-', '2.0.2')))]", + "version": "2.0.2" } }, { @@ -3281,9 +3258,9 @@ "location": "[parameters('workspace-location')]", "properties": { "eTag": "*", - "displayName": "Bots added to multiple teams", + "displayName": "GSA Enriched Office 365 - Bots added to multiple teams", "category": "Hunting Queries", - "query": "// Adjust these thresholds to suit your environment.\nlet threshold = 2;\nlet time_threshold = timespan(5m);\nEnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"BotAddedToTeam\"\n | extend TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n | summarize Start = max(TimeGenerated), End = min(TimeGenerated), Teams = make_set(TeamName, 10000) by UserId\n | extend CountOfTeams = array_length(Teams)\n | extend TimeDelta = End - Start\n | where CountOfTeams > threshold\n | where TimeDelta <= time_threshold\n | project Start, End, Teams, CountOfTeams, UserId\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix\n", + "query": "let threshold = 2; // Adjust this threshold based on your environment\nlet time_threshold = timespan(5m); // Adjust the time delta threshold\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"BotAddedToTeam\"\n | summarize Start = max(TimeGenerated), End = min(TimeGenerated), Teams = make_set(TeamName, 10000) by UserId\n | extend CountOfTeams = array_length(Teams)\n | extend TimeDelta = End - Start\n | where CountOfTeams > threshold\n | where TimeDelta >= time_threshold\n | project Start, End, Teams, CountOfTeams, UserId\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"BotAddedToTeam\"\n | extend TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n | summarize Start = max(TimeGenerated), End = min(TimeGenerated), Teams = make_set(TeamName, 10000) by UserId\n | extend CountOfTeams = array_length(Teams)\n | extend TimeDelta = End - Start\n | where CountOfTeams > threshold\n | where TimeDelta <= time_threshold\n | project Start, End, Teams, CountOfTeams, UserId\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(Start, *) by UserId;\n// Final Output\nCombinedEvents\n | project Start, End, Teams, CountOfTeams, UserId, AccountName, AccountUPNSuffix\n | order by Start desc\n", "version": 2, "tags": [ { @@ -3337,7 +3314,7 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('huntingQueryObject5')._huntingQuerycontentId5]", "contentKind": "HuntingQuery", - "displayName": "Bots added to multiple teams", + "displayName": "GSA Enriched Office 365 - Bots added to multiple teams", "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject5')._huntingQuerycontentId5,'-', '2.0.1')))]", "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject5')._huntingQuerycontentId5,'-', '2.0.1')))]", "version": "2.0.1" @@ -3366,9 +3343,9 @@ "location": "[parameters('workspace-location')]", "properties": { "eTag": "*", - "displayName": "User made Owner of multiple teams", + "displayName": "GSA Enriched Office 365 - User made Owner of multiple teams", "category": "Hunting Queries", - "query": "// Adjust this value to change how many teams a user is made owner of before detecting\nlet max_owner_count = 3;\n// Identify users who have been made owner of multiple Teams\nlet high_owner_count = (\n EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"MemberRoleChanged\"\n | extend Member = tostring(UserId)\n | extend NewRole = toint(parse_json(tostring(AdditionalProperties)).Role)\n | where NewRole == 2\n | summarize TeamCount = dcount(ObjectId) by Member\n | where TeamCount > max_owner_count\n | project Member\n);\nEnrichedMicrosoft365AuditLogs\n| where Workload == \"MicrosoftTeams\"\n| where Operation == \"MemberRoleChanged\"\n| extend Member = tostring(UserId)\n| extend NewRole = toint(parse_json(tostring(AdditionalProperties)).Role)\n| where NewRole == 2\n| where Member in (high_owner_count)\n| extend AccountName = tostring(split(Member, \"@\")[0]), AccountUPNSuffix = tostring(split(Member, \"@\")[1])\n", + "query": "let max_owner_count = 3; \n// Adjust this value to change how many teams a user is made owner of before detecting\n// OfficeActivity Query: Identify users who have been made owners of more than 'max_owner_count' teams\nlet high_owner_count_office = (\n OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberRoleChanged\"\n | extend Member = tostring(parse_json(Members)[0].UPN) \n | extend NewRole = toint(parse_json(Members)[0].Role)\n | where NewRole == 2 // Role 2 corresponds to \"Owner\"\n | summarize TeamCount = dcount(TeamName) by Member\n | where TeamCount > max_owner_count\n | project Member\n);\n// OfficeActivity Query: Fetch details for users with high ownership count\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberRoleChanged\"\n | extend Member = tostring(parse_json(Members)[0].UPN)\n | extend NewRole = toint(parse_json(Members)[0].Role)\n | where NewRole == 2 // Role 2 corresponds to \"Owner\"\n | where Member in (high_owner_count_office)\n | extend AccountName = tostring(split(Member, \"@\")[0]), AccountUPNSuffix = tostring(split(Member, \"@\")[1]);\n// EnrichedMicrosoft365AuditLogs Query: Identify users who have been made owners of more than 'max_owner_count' teams\nlet high_owner_count_enriched = (\n EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"MemberRoleChanged\"\n | extend Member = tostring(UserId)\n | extend NewRole = toint(parse_json(tostring(AdditionalProperties)).Role)\n | where NewRole == 2 // Role 2 corresponds to \"Owner\"\n | summarize TeamCount = dcount(ObjectId) by Member\n | where TeamCount > max_owner_count\n | project Member\n);\n// EnrichedMicrosoft365AuditLogs Query: Fetch details for users with high ownership count\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"MemberRoleChanged\"\n | extend Member = tostring(UserId)\n | extend NewRole = toint(parse_json(tostring(AdditionalProperties)).Role)\n | where NewRole == 2 // Role 2 corresponds to \"Owner\"\n | where Member in (high_owner_count_enriched)\n | extend AccountName = tostring(split(Member, \"@\")[0]), AccountUPNSuffix = tostring(split(Member, \"@\")[1]);\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize by Member, AccountName, AccountUPNSuffix;\n// Final Output\nCombinedEvents\n| order by Member asc\n| project Member, AccountName, AccountUPNSuffix\n", "version": 2, "tags": [ { @@ -3422,10 +3399,10 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('huntingQueryObject6')._huntingQuerycontentId6]", "contentKind": "HuntingQuery", - "displayName": "User made Owner of multiple teams", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject6')._huntingQuerycontentId6,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject6')._huntingQuerycontentId6,'-', '2.0.1')))]", - "version": "2.0.1" + "displayName": "GSA Enriched Office 365 - User made Owner of multiple teams", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject6')._huntingQuerycontentId6,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject6')._huntingQuerycontentId6,'-', '2.0.2')))]", + "version": "2.0.2" } }, { @@ -3451,9 +3428,9 @@ "location": "[parameters('workspace-location')]", "properties": { "eTag": "*", - "displayName": "Multiple Teams deleted by a single user", + "displayName": "GSA Enriched Office 365 - Multiple Teams deleted by a single user", "category": "Hunting Queries", - "query": "// Adjust this value to change how many Teams should be deleted before including\nlet max_delete = 3;\nlet deleting_users = (\n EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"TeamDeleted\"\n | summarize count_ = count() by UserId\n | where count_ > max_delete\n | project UserId\n);\nEnrichedMicrosoft365AuditLogs\n| where Workload == \"MicrosoftTeams\"\n| where Operation == \"TeamDeleted\"\n| where UserId in (deleting_users)\n| extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n| extend Account_0_Name = AccountName\n| extend Account_0_UPNSuffix = AccountUPNSuffix\n", + "query": "let max_delete = 3; // Adjust this value to change how many Teams should be deleted before being included\n// EnrichedMicrosoft365AuditLogs - Users who deleted more than 'max_delete' Teams\nlet deleting_users_enriched = (\n EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"TeamDeleted\"\n | summarize count_ = count() by UserId\n | where count_ > max_delete\n | project UserId\n);\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"TeamDeleted\"\n | where UserId in (deleting_users_enriched)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix\n | project TimeGenerated, UserId, Account_0_Name, Account_0_UPNSuffix;\n// OfficeActivity - Users who deleted more than 'max_delete' Teams\nlet deleting_users_office = (\n OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"TeamDeleted\"\n | summarize count_ = count() by UserId\n | where count_ > max_delete\n | project UserId\n);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"TeamDeleted\"\n | where UserId in (deleting_users_office)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix\n | project TimeGenerated, UserId, Account_0_Name, Account_0_UPNSuffix;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeGenerated, *) by UserId;\n// Final Output\nCombinedEvents\n| project TimeGenerated, UserId, Account_0_Name, Account_0_UPNSuffix\n| order by TimeGenerated desc\n", "version": 2, "tags": [ { @@ -3507,10 +3484,10 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('huntingQueryObject7')._huntingQuerycontentId7]", "contentKind": "HuntingQuery", - "displayName": "Multiple Teams deleted by a single user", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject7')._huntingQuerycontentId7,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject7')._huntingQuerycontentId7,'-', '2.0.1')))]", - "version": "2.0.1" + "displayName": "GSA Enriched Office 365 - Multiple Teams deleted by a single user", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject7')._huntingQuerycontentId7,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject7')._huntingQuerycontentId7,'-', '2.0.2')))]", + "version": "2.0.2" } }, { @@ -3536,9 +3513,9 @@ "location": "[parameters('workspace-location')]", "properties": { "eTag": "*", - "displayName": "Previously Unseen Bot or Application Added to Teams", + "displayName": "GSA Enriched Office 365 - Previously Unseen Bot or Application Added to Teams", "category": "Hunting Queries", - "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = starttime - 14d;\nlet historical_bots = \n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (lookback .. starttime)\n | where Workload == \"MicrosoftTeams\"\n | extend AddonName = tostring(parse_json(tostring(AdditionalProperties)).AddonName)\n | where isnotempty(AddonName)\n | distinct AddonName;\nEnrichedMicrosoft365AuditLogs\n| where TimeGenerated between (starttime .. endtime)\n| where Workload == \"MicrosoftTeams\"\n| extend AddonName = tostring(parse_json(tostring(AdditionalProperties)).AddonName)\n| where AddonName !in (historical_bots)\n| extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n", + "query": "let starttime = todatetime('{{StartTimeISO}}');\n let endtime = todatetime('{{EndTimeISO}}');\n let lookback = starttime - 14d;\n // Historical bots from EnrichedMicrosoft365AuditLogs\n let historical_bots_enriched = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (lookback .. starttime)\n | where Workload == \"MicrosoftTeams\"\n | extend AddonName = tostring(parse_json(tostring(AdditionalProperties)).AddonName)\n | where isnotempty(AddonName)\n | distinct AddonName;\n // Historical bots from OfficeActivity\n let historical_bots_office = OfficeActivity\n | where TimeGenerated between (lookback .. starttime)\n | where OfficeWorkload == \"MicrosoftTeams\"\n | where isnotempty(AddonName)\n | distinct AddonName;\n // Find new bots in Enriched Logs\n let new_bots_enriched = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where Workload == \"MicrosoftTeams\"\n | extend AddonName = tostring(parse_json(tostring(AdditionalProperties)).AddonName)\n | where AddonName !in (historical_bots_enriched)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n // Find new bots in OfficeActivity\n let new_bots_office = OfficeActivity\n | where TimeGenerated between (starttime .. endtime)\n | where OfficeWorkload == \"MicrosoftTeams\"\n | where isnotempty(AddonName)\n | where AddonName !in (historical_bots_office)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n // Combine both new bots from Enriched Logs and OfficeActivity\n let CombinedNewBots = new_bots_enriched\n | union new_bots_office\n | summarize arg_min(TimeGenerated, *) by AddonName, UserId;\n // Final output\n CombinedNewBots\n | project TimeGenerated, AddonName, UserId, AccountName, AccountUPNSuffix\n | order by TimeGenerated desc\n", "version": 2, "tags": [ { @@ -3592,10 +3569,10 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('huntingQueryObject8')._huntingQuerycontentId8]", "contentKind": "HuntingQuery", - "displayName": "Previously Unseen Bot or Application Added to Teams", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject8')._huntingQuerycontentId8,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject8')._huntingQuerycontentId8,'-', '2.0.1')))]", - "version": "2.0.1" + "displayName": "GSA Enriched Office 365 - Previously Unseen Bot or Application Added to Teams", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject8')._huntingQuerycontentId8,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject8')._huntingQuerycontentId8,'-', '2.0.2')))]", + "version": "2.0.2" } }, { @@ -3621,9 +3598,9 @@ "location": "[parameters('workspace-location')]", "properties": { "eTag": "*", - "displayName": "New Windows Reserved Filenames staged on Office file services", + "displayName": "GSA Enriched Office 365 - New Windows Reserved Filenames staged on Office file services", "category": "Hunting Queries", - "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = totimespan((endtime - starttime) * 7);\nlet Reserved = dynamic(['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']);\nEnrichedMicrosoft365AuditLogs\n| where TimeGenerated between (starttime .. endtime)\n| extend FileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n| extend ClientUserAgent = tostring(parse_json(tostring(AdditionalProperties)).ClientUserAgent)\n| extend SiteUrl = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n| where isnotempty(ObjectId)\n| where ObjectId !~ FileName\n| where ObjectId in (Reserved) or FileName in (Reserved)\n| where ClientUserAgent !has \"Mac OS\"\n| project TimeGenerated, Id, Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, ClientUserAgent, SiteUrl, ObjectId, FileName\n| join kind=leftanti (\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (ago(lookback) .. starttime)\n | extend FileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | extend ClientUserAgent = tostring(parse_json(tostring(AdditionalProperties)).ClientUserAgent)\n | extend SiteUrl = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(ObjectId)\n | where ObjectId !~ FileName\n | where ObjectId in (Reserved) or FileName in (Reserved)\n | where ClientUserAgent !has \"Mac OS\"\n | summarize PrevSeenCount = count() by ObjectId, UserId, FileName\n) on ObjectId\n| extend SiteUrlUserFolder = tolower(split(SiteUrl, '/')[-2])\n| extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n| extend UserIdDiffThanUserFolder = iff(SiteUrl has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n| summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(ClientUserAgent, 100000),\n Ids = make_list(Id, 100000), SourceRelativeUrls = make_list(ObjectId, 100000), FileNames = make_list(FileName, 100000)\n by Workload, RecordType, UserType, UserKey, UserId, ClientIp, SiteUrl, ObjectId, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n| extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n| extend IP_0_Address = ClientIp\n| extend Account_0_Name = AccountName\n| extend Account_0_UPNSuffix = AccountUPNSuffix\n| extend URL_0_Url = SiteUrl\n", + "query": "let starttime = todatetime('{{StartTimeISO}}');\n let endtime = todatetime('{{EndTimeISO}}');\n let lookback = totimespan((endtime - starttime) * 7);\n \n // Reserved file names and extensions for Windows\n let Reserved = dynamic(['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']);\n \n // EnrichedMicrosoft365AuditLogs Query\n let EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | extend FileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | extend ClientUserAgent = tostring(parse_json(tostring(AdditionalProperties)).ClientUserAgent)\n | extend SiteUrl = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(ObjectId)\n | where ObjectId !~ FileName\n | where ObjectId in (Reserved) or FileName in (Reserved)\n | where ClientUserAgent !has \"Mac OS\"\n | join kind=leftanti (\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (ago(lookback) .. starttime)\n | extend FileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | extend ClientUserAgent = tostring(parse_json(tostring(AdditionalProperties)).ClientUserAgent)\n | extend SiteUrl = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(ObjectId)\n | where ObjectId !~ FileName\n | where ObjectId in (Reserved) or FileName in (Reserved)\n | where ClientUserAgent !has \"Mac OS\"\n | summarize PrevSeenCount = count() by ObjectId, UserId, FileName\n ) on ObjectId\n | extend SiteUrlUserFolder = tolower(split(SiteUrl, '/')[-2])\n | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n | extend UserIdDiffThanUserFolder = iff(SiteUrl has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(ClientUserAgent, 100000), Ids = make_list(Id, 100000),\n SourceRelativeUrls = make_list(ObjectId, 100000), FileNames = make_list(FileName, 100000) by Workload, RecordType, UserType, UserKey, UserId, ClientIp, SiteUrl, ObjectId, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIp\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, URL_0_Url = SiteUrl;\n // OfficeActivity Query\n let OfficeEvents = OfficeActivity\n | where TimeGenerated between (starttime .. endtime)\n | where isnotempty(SourceFileExtension)\n | where SourceFileName !~ SourceFileExtension\n | where SourceFileExtension in (Reserved) or SourceFileName in (Reserved)\n | where UserAgent !has \"Mac OS\"\n | join kind=leftanti (\n OfficeActivity\n | where TimeGenerated between (ago(lookback) .. starttime)\n | where isnotempty(SourceFileExtension)\n | where SourceFileName !~ SourceFileExtension\n | where SourceFileExtension in (Reserved) or SourceFileName in (Reserved)\n | where UserAgent !has \"Mac OS\"\n | summarize PrevSeenCount = count() by SourceFileExtension\n ) on SourceFileExtension\n | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])\n | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(UserAgent, 100000), OfficeIds = make_list(OfficeId, 100000),\n SourceRelativeUrls = make_list(SourceRelativeUrl, 100000), FileNames = make_list(SourceFileName, 100000) by OfficeWorkload, RecordType, UserType, UserKey, UserId, ClientIP, Site_Url, SourceFileExtension, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIP\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, URL_0_Url = Site_Url;\n // Combine Office and Enriched Logs\n let CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by UserId, ClientIP;\n // Final Output\n CombinedEvents\n | project StartTime, EndTime, Operations, UserAgents, IP_0_Address, Account_0_Name, Account_0_UPNSuffix, URL_0_Url\n | order by StartTime desc\n", "version": 2, "tags": [ { @@ -3677,10 +3654,10 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('huntingQueryObject9')._huntingQuerycontentId9]", "contentKind": "HuntingQuery", - "displayName": "New Windows Reserved Filenames staged on Office file services", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject9')._huntingQuerycontentId9,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject9')._huntingQuerycontentId9,'-', '2.0.1')))]", - "version": "2.0.1" + "displayName": "GSA Enriched Office 365 - New Windows Reserved Filenames staged on Office file services", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject9')._huntingQuerycontentId9,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject9')._huntingQuerycontentId9,'-', '2.0.2')))]", + "version": "2.0.2" } }, { @@ -3706,9 +3683,9 @@ "location": "[parameters('workspace-location')]", "properties": { "eTag": "*", - "displayName": "Office Mail Forwarding - Hunting Version", + "displayName": "GSA Enriched Office 365 - Office Mail Forwarding - Hunting Version", "category": "Hunting Queries", - "query": "EnrichedMicrosoft365AuditLogs\n| where Workload == \"Exchange\"\n| where (Operation == \"Set-Mailbox\" and tostring(parse_json(tostring(AdditionalProperties))) contains 'ForwardingSmtpAddress') \n or (Operation in ('New-InboxRule', 'Set-InboxRule') and (tostring(parse_json(tostring(AdditionalProperties))) contains 'ForwardTo' or tostring(parse_json(tostring(AdditionalProperties))) contains 'RedirectTo'))\n| extend parsed = parse_json(tostring(AdditionalProperties))\n| extend fwdingDestination_initial = iif(Operation == \"Set-Mailbox\", tostring(parsed.ForwardingSmtpAddress), coalesce(tostring(parsed.ForwardTo), tostring(parsed.RedirectTo)))\n| where isnotempty(fwdingDestination_initial)\n| extend fwdingDestination = iff(fwdingDestination_initial has \"smtp\", (split(fwdingDestination_initial, \":\")[1]), fwdingDestination_initial)\n| parse fwdingDestination with * '@' ForwardedtoDomain \n| parse UserId with *'@' UserDomain\n| extend subDomain = ((split(strcat(tostring(split(UserDomain, '.')[-2]), '.', tostring(split(UserDomain, '.')[-1])), '.'))[0])\n| where ForwardedtoDomain !contains subDomain\n| extend Result = iff(ForwardedtoDomain != UserDomain, \"Mailbox rule created to forward to External Domain\", \"Forward rule for Internal domain\")\n| extend ClientIPAddress = case(ClientIp has \".\", tostring(split(ClientIp, \":\")[0]), ClientIp has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIp, \"]\")[0]))), ClientIp)\n| extend Port = case(\n ClientIp has \".\",\n (split(ClientIp, \":\")[1]),\n ClientIp has \"[\",\n tostring(split(ClientIp, \"]:\")[1]),\n ClientIp\n )\n| project\n TimeGenerated,\n UserId,\n UserDomain,\n subDomain,\n Operation,\n ForwardedtoDomain,\n ClientIPAddress,\n Result,\n Port,\n ObjectId,\n fwdingDestination,\n AdditionalProperties\n| extend\n AccountName = tostring(split(UserId, \"@\")[0]),\n AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n| extend Host = tostring(parse_json(tostring(AdditionalProperties)).OriginatingServer)\n| extend HostName = tostring(split(Host, \".\")[0])\n| extend DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))\n| extend Account_0_Name = AccountName\n| extend Account_0_UPNSuffix = AccountUPNSuffix\n| extend IP_0_Address = ClientIPAddress\n| extend Host_0_HostName = HostName\n| extend Host_0_DnsDomain = DnsDomain\n", + "query": "let starttime = todatetime('{{StartTimeISO}}');\n let endtime = todatetime('{{EndTimeISO}}');\n \n // Enriched Logs Query for forwarding rule operations\n let EnrichedForwardRules = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where Workload == \"Exchange\"\n | where (Operation == \"Set-Mailbox\" and tostring(parse_json(tostring(AdditionalProperties))) contains 'ForwardingSmtpAddress') \n or (Operation in ('New-InboxRule', 'Set-InboxRule') and (tostring(parse_json(tostring(AdditionalProperties))) contains 'ForwardTo' or tostring(parse_json(tostring(AdditionalProperties))) contains 'RedirectTo'))\n | extend parsed = parse_json(tostring(AdditionalProperties))\n | extend fwdingDestination_initial = iif(Operation == \"Set-Mailbox\", tostring(parsed.ForwardingSmtpAddress), coalesce(tostring(parsed.ForwardTo), tostring(parsed.RedirectTo)))\n | where isnotempty(fwdingDestination_initial)\n | extend fwdingDestination = iff(fwdingDestination_initial has \"smtp\", (split(fwdingDestination_initial, \":\")[1]), fwdingDestination_initial)\n | parse fwdingDestination with * '@' ForwardedtoDomain \n | parse UserId with * '@' UserDomain\n | extend subDomain = ((split(strcat(tostring(split(UserDomain, '.')[-2]), '.', tostring(split(UserDomain, '.')[-1])), '.'))[0])\n | where ForwardedtoDomain !contains subDomain\n | extend Result = iff(ForwardedtoDomain != UserDomain, \"Mailbox rule created to forward to External Domain\", \"Forward rule for Internal domain\")\n | extend ClientIPAddress = case(ClientIp has \".\", tostring(split(ClientIp, \":\")[0]), ClientIp has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIp, \"]\")[0]))), ClientIp)\n | extend Port = case(ClientIp has \".\", (split(ClientIp, \":\")[1]), ClientIp has \"[\", tostring(split(ClientIp, \"]:\")[1]), ClientIp)\n | extend Host = tostring(parse_json(tostring(AdditionalProperties)).OriginatingServer)\n | extend HostName = tostring(split(Host, \".\")[0])\n | extend DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))\n | project TimeGenerated, UserId, UserDomain, subDomain, Operation, ForwardedtoDomain, ClientIPAddress, Result, Port, ObjectId, fwdingDestination, HostName, DnsDomain\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIPAddress, Host_0_HostName = HostName, Host_0_DnsDomain = DnsDomain, Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n // Office Activity Query for forwarding rule operations\n let OfficeForwardRules = OfficeActivity\n | where TimeGenerated between (starttime .. endtime)\n | where OfficeWorkload == \"Exchange\"\n | where (Operation =~ \"Set-Mailbox\" and Parameters contains 'ForwardingSmtpAddress') \n or (Operation in~ ('New-InboxRule', 'Set-InboxRule') and (Parameters contains 'ForwardTo' or Parameters contains 'RedirectTo'))\n | extend parsed = parse_json(Parameters)\n | extend fwdingDestination_initial = (iif(Operation =~ \"Set-Mailbox\", tostring(parsed[1].Value), tostring(parsed[2].Value)))\n | where isnotempty(fwdingDestination_initial)\n | extend fwdingDestination = iff(fwdingDestination_initial has \"smtp\", (split(fwdingDestination_initial, \":\")[1]), fwdingDestination_initial)\n | parse fwdingDestination with * '@' ForwardedtoDomain \n | parse UserId with * '@' UserDomain\n | extend subDomain = ((split(strcat(tostring(split(UserDomain, '.')[-2]), '.', tostring(split(UserDomain, '.')[-1])), '.') [0]))\n | where ForwardedtoDomain !contains subDomain\n | extend Result = iff(ForwardedtoDomain != UserDomain, \"Mailbox rule created to forward to External Domain\", \"Forward rule for Internal domain\")\n | extend ClientIPAddress = case(ClientIP has \".\", tostring(split(ClientIP, \":\")[0]), ClientIP has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIP, \"]\")[0]))), ClientIP)\n | extend Port = case(ClientIP has \".\", (split(ClientIP, \":\")[1]), ClientIP has \"[\", tostring(split(ClientIP, \"]:\")[1]), ClientIP)\n | extend Host = tostring(split(OriginatingServer, \" (\")[0])\n | extend HostName = tostring(split(Host, \".\")[0])\n | extend DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))\n | project TimeGenerated, UserId, UserDomain, subDomain, Operation, ForwardedtoDomain, ClientIPAddress, Result, Port, HostName, DnsDomain\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIPAddress, Host_0_HostName = HostName, Host_0_DnsDomain = DnsDomain, Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n // Combine the results from both Enriched and Office Activity logs\n let CombinedForwardRules = EnrichedForwardRules\n | union OfficeForwardRules\n | summarize arg_min(TimeGenerated, *) by UserId, ForwardedtoDomain, Operation\n | project TimeGenerated, UserId, UserDomain, subDomain, Operation, ForwardedtoDomain, ClientIPAddress, Result, Port, ObjectId, fwdingDestination, Host_0_HostName, Host_0_DnsDomain, IP_0_Address, Account_0_Name, Account_0_UPNSuffix; \n // Final output\n CombinedForwardRules\n | order by TimeGenerated desc;\n", "version": 2, "tags": [ { @@ -3762,10 +3739,10 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('huntingQueryObject10')._huntingQuerycontentId10]", "contentKind": "HuntingQuery", - "displayName": "Office Mail Forwarding - Hunting Version", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject10')._huntingQuerycontentId10,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject10')._huntingQuerycontentId10,'-', '2.0.1')))]", - "version": "2.0.1" + "displayName": "GSA Enriched Office 365 - Office Mail Forwarding - Hunting Version", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject10')._huntingQuerycontentId10,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject10')._huntingQuerycontentId10,'-', '2.0.2')))]", + "version": "2.0.2" } }, { @@ -3791,9 +3768,9 @@ "location": "[parameters('workspace-location')]", "properties": { "eTag": "*", - "displayName": "Files uploaded to teams and access summary", + "displayName": "GSA Enriched Office 365 - Files uploaded to teams and access summary", "category": "Hunting Queries", - "query": "EnrichedMicrosoft365AuditLogs\n| where RecordType == \"SharePointFileOperation\"\n| where Operation == \"FileUploaded\"\n| where UserId != \"app@sharepoint\"\n| where ObjectId has \"Microsoft Teams Chat Files\"\n| extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n| join kind=leftouter (\n EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation == \"FileDownloaded\" or Operation == \"FileAccessed\"\n | where UserId != \"app@sharepoint\"\n | where ObjectId has \"Microsoft Teams Chat Files\"\n | extend UserId1 = UserId, ClientIp1 = ClientIp\n) on ObjectId\n| extend userBag = bag_pack(\"UserId1\", UserId1, \"ClientIp1\", ClientIp1)\n| summarize AccessedBy = make_bag(userBag), make_set(UserId1, 10000) by bin(TimeGenerated, 1h), UserId, ObjectId, SourceFileName\n| extend NumberOfUsersAccessed = array_length(bag_keys(AccessedBy))\n| project timestamp = TimeGenerated, UserId, FileLocation = ObjectId, FileName = SourceFileName, AccessedBy, NumberOfUsersAccessed\n| extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n| extend Account_0_Name = AccountName\n| extend Account_0_UPNSuffix = AccountUPNSuffix\n", + "query": "// Define the query for EnrichedMicrosoft365AuditLogs\nlet enrichedLogs = EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation == \"FileUploaded\"\n | where UserId != \"app@sharepoint\"\n | where ObjectId has \"Microsoft Teams Chat Files\"\n | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | join kind=leftouter (\n EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileAccessed\")\n | where UserId != \"app@sharepoint\"\n | where ObjectId has \"Microsoft Teams Chat Files\"\n | extend UserId1 = UserId, ClientIp1 = ClientIp\n ) on ObjectId\n | extend userBag = bag_pack(\"UserId1\", UserId1, \"ClientIp1\", ClientIp1)\n | summarize AccessedBy = make_bag(userBag), make_set(UserId1, 10000) by bin(TimeGenerated, 1h), UserId, ObjectId, SourceFileName\n | extend NumberOfUsersAccessed = array_length(bag_keys(AccessedBy))\n | project timestamp = TimeGenerated, UserId, FileLocation = ObjectId, FileName = SourceFileName, AccessedBy, NumberOfUsersAccessed\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix;\n// Define the query for OfficeActivity\nlet officeLogs = OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where Operation == \"FileUploaded\"\n | where UserId != \"app@sharepoint\"\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | join kind=leftouter (\n OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileAccessed\")\n | where UserId != \"app@sharepoint\"\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n ) on OfficeObjectId\n | extend userBag = bag_pack(UserId1, ClientIP1)\n | summarize AccessedBy = make_bag(userBag, 10000), make_set(UserId1, 10000) by TimeGenerated, UserId, OfficeObjectId, SourceFileName\n | extend NumberUsers = array_length(bag_keys(AccessedBy))\n | project timestamp = TimeGenerated, UserId, FileLocation = OfficeObjectId, FileName = SourceFileName, AccessedBy, NumberOfUsersAccessed = NumberUsers\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix;\n// Union both results\nenrichedLogs\n| union officeLogs\n| order by timestamp desc;\n", "version": 2, "tags": [ { @@ -3847,10 +3824,10 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('huntingQueryObject11')._huntingQuerycontentId11]", "contentKind": "HuntingQuery", - "displayName": "Files uploaded to teams and access summary", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject11')._huntingQuerycontentId11,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject11')._huntingQuerycontentId11,'-', '2.0.1')))]", - "version": "2.0.1" + "displayName": "GSA Enriched Office 365 - Files uploaded to teams and access summary", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject11')._huntingQuerycontentId11,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject11')._huntingQuerycontentId11,'-', '2.0.2')))]", + "version": "2.0.2" } }, { @@ -3876,9 +3853,9 @@ "location": "[parameters('workspace-location')]", "properties": { "eTag": "*", - "displayName": "User added to Teams and immediately uploads file", + "displayName": "GSA Enriched Office 365 - User added to Teams and immediately uploads file", "category": "Hunting Queries", - "query": "let threshold = 1m;\nlet MemberAddedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"MemberAdded\"\n | extend TeamName = tostring(parse_json(AdditionalProperties).TeamName)\n | project TimeGenerated, UploaderID = UserId, TeamName;\nlet FileUploadEvents = EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where ObjectId has \"Microsoft Teams Chat Files\"\n | where Operation == \"FileUploaded\"\n | extend SourceFileName = tostring(parse_json(AdditionalProperties).SourceFileName)\n | project UploadTime = TimeGenerated, UploaderID = UserId, FileLocation = ObjectId, SourceFileName;\nMemberAddedEvents\n | join kind=inner (FileUploadEvents) on UploaderID\n | where UploadTime > TimeGenerated and UploadTime < TimeGenerated + threshold\n | extend timestamp = TimeGenerated, AccountCustomEntity = UploaderID\n", + "query": "let threshold = 1m;\n // Define MemberAddedEvents for EnrichedMicrosoft365AuditLogs\n let MemberAddedEvents_Enriched = EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"MemberAdded\"\n | extend TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n | project TimeGenerated, UploaderID = UserId, TeamName;\n // Define FileUploadEvents for EnrichedMicrosoft365AuditLogs\n let FileUploadEvents_Enriched = EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where ObjectId has \"Microsoft Teams Chat Files\"\n | where Operation == \"FileUploaded\"\n | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | project UploadTime = TimeGenerated, UploaderID = UserId, FileLocation = ObjectId, SourceFileName;\n // Perform join for EnrichedMicrosoft365AuditLogs\n let EnrichedResults = MemberAddedEvents_Enriched\n | join kind=inner (FileUploadEvents_Enriched) on UploaderID\n | where UploadTime > TimeGenerated and UploadTime < TimeGenerated + threshold\n | extend timestamp = TimeGenerated, AccountCustomEntity = UploaderID;\n // Define MemberAddedEvents for OfficeActivity\n let MemberAddedEvents_Office = OfficeActivity\n | where OfficeWorkload == \"MicrosoftTeams\"\n | where Operation == \"MemberAdded\"\n | extend TeamName = iff(isempty(TeamName), Members[0].UPN, TeamName)\n | project TimeGenerated, UploaderID = UserId, TeamName;\n // Define FileUploadEvents for OfficeActivity\n let FileUploadEvents_Office = OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | where Operation == \"FileUploaded\"\n | project UploadTime = TimeGenerated, UploaderID = UserId, FileLocation = OfficeObjectId, FileName = SourceFileName;\n // Perform join for OfficeActivity\n let OfficeResults = MemberAddedEvents_Office\n | join kind=inner (FileUploadEvents_Office) on UploaderID\n | where UploadTime > TimeGenerated and UploadTime < TimeGenerated + threshold\n | project-away UploaderID1\n | extend timestamp = TimeGenerated, AccountCustomEntity = UploaderID;\n // Union both results\n EnrichedResults\n | union OfficeResults\n | order by timestamp desc;\n", "version": 2, "tags": [ { @@ -3932,10 +3909,10 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('huntingQueryObject12')._huntingQuerycontentId12]", "contentKind": "HuntingQuery", - "displayName": "User added to Teams and immediately uploads file", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject12')._huntingQuerycontentId12,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject12')._huntingQuerycontentId12,'-', '2.0.1')))]", - "version": "2.0.1" + "displayName": "GSA Enriched Office 365 - User added to Teams and immediately uploads file", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject12')._huntingQuerycontentId12,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject12')._huntingQuerycontentId12,'-', '2.0.2')))]", + "version": "2.0.2" } }, { @@ -3961,9 +3938,9 @@ "location": "[parameters('workspace-location')]", "properties": { "eTag": "*", - "displayName": "Windows Reserved Filenames Staged on Office File Services", + "displayName": "GSA Enriched Office 365 - Windows Reserved Filenames Staged on Office File Services", "category": "Hunting Queries", - "query": "// Reserved FileNames/Extension for Windows\nlet Reserved = dynamic(['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']);\nEnrichedMicrosoft365AuditLogs\n| extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n| extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n| extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n| where isnotempty(ObjectId)\n| where ObjectId in (Reserved) or SourceFileName in (Reserved)\n| where UserAgent !has \"Mac OS\"\n| extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])\n| extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n// identify when UserId is not a match to the specific site url personal folder reference\n| extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n| summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(UserAgent, 100000), ObjectIds = make_list(Id, 100000), SourceRelativeUrls = make_list(ObjectId, 100000), FileNames = make_list(SourceFileName, 100000)\nby Workload, RecordType, UserType, UserKey, UserId, ClientIp, Site_Url, ObjectId, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n// Use mvexpand on any list items and you can expand out the exact time and other metadata about the hit\n| extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n| extend IP_0_Address = ClientIp\n| extend Account_0_Name = AccountName\n| extend Account_0_UPNSuffix = AccountUPNSuffix\n| extend URL_0_Url = Site_Url\n", + "query": "let Reserved = dynamic(['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']);\n // Query for OfficeActivity\n let OfficeActivityResults = OfficeActivity\n | where isnotempty(SourceFileExtension)\n | where SourceFileExtension in (Reserved) or SourceFileName in (Reserved)\n | where UserAgent !has \"Mac OS\"\n | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])\n | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(UserAgent, 100000), OfficeIds = make_list(OfficeId, 100000), SourceRelativeUrls = make_list(SourceRelativeUrl, 100000), FileNames = make_list(SourceFileName, 100000)\n by OfficeWorkload, RecordType, UserType, UserKey, UserId, ClientIP, Site_Url, SourceFileExtension, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIP\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix\n | extend URL_0_Url = Site_Url;\n // Query for EnrichedMicrosoft365AuditLogs\n let EnrichedMicrosoft365Results = EnrichedMicrosoft365AuditLogs\n | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(ObjectId)\n | where ObjectId in (Reserved) or SourceFileName in (Reserved)\n | where UserAgent !has \"Mac OS\"\n | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])\n | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(UserAgent, 100000), ObjectIds = make_list(Id, 100000), SourceRelativeUrls = make_list(ObjectId, 100000), FileNames = make_list(SourceFileName, 100000)\n by Workload, RecordType, UserType, UserKey, UserId, ClientIp, Site_Url, ObjectId, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIp\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix\n | extend URL_0_Url = Site_Url;\n // Combine both queries\n OfficeActivityResults\n | union EnrichedMicrosoft365Results\n | order by StartTime desc;\n", "version": 2, "tags": [ { @@ -4017,10 +3994,10 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('huntingQueryObject13')._huntingQuerycontentId13]", "contentKind": "HuntingQuery", - "displayName": "Windows Reserved Filenames Staged on Office File Services", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject13')._huntingQuerycontentId13,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject13')._huntingQuerycontentId13,'-', '2.0.1')))]", - "version": "2.0.1" + "displayName": "GSA Enriched Office 365 - Windows Reserved Filenames Staged on Office File Services", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject13')._huntingQuerycontentId13,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject13')._huntingQuerycontentId13,'-', '2.0.2')))]", + "version": "2.0.2" } }, { @@ -4216,9 +4193,9 @@ "location": "[parameters('workspace-location')]", "properties": { "eTag": "*", - "displayName": "SharePointFileOperation via previously unseen IPs", + "displayName": "GSA Enriched Office 365 - SharePointFileOperation via previously unseen IPs", "category": "Hunting Queries", - "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = starttime - 14d;\nlet BLOCK_THRESHOLD = 1.0;\nlet HighBlockRateASNs =\n SigninLogs\n | where TimeGenerated > lookback\n | where isnotempty(AutonomousSystemNumber)\n | summarize make_set(IPAddress), TotalIps = dcount(IPAddress), BlockedSignins = countif(ResultType == \"50053\"), TotalSignins = count() by AutonomousSystemNumber\n | extend BlockRatio = 1.00 * BlockedSignins / TotalSignins\n | where BlockRatio >= BLOCK_THRESHOLD\n | distinct AutonomousSystemNumber;\nlet ASNIPs =\n SigninLogs\n | where TimeGenerated > lookback\n | where AutonomousSystemNumber in (HighBlockRateASNs)\n | distinct IPAddress, AutonomousSystemNumber;\nEnrichedMicrosoft365AuditLogs\n| where TimeGenerated between (starttime .. endtime)\n| where RecordType == \"SharePointFileOperation\"\n| where Operation in (\"FileDownloaded\", \"FileUploaded\")\n| where ClientIp in (ASNIPs)\n| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by ClientIp\n| extend IP_0_Address = ClientIp\n", + "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = starttime - 14d;\nlet BLOCK_THRESHOLD = 1.0;\n// Identify Autonomous System Numbers (ASNs) with a high block rate in Sign-in Logs\nlet HighBlockRateASNs = SigninLogs\n | where TimeGenerated > lookback\n | where isnotempty(AutonomousSystemNumber)\n | summarize make_set(IPAddress), TotalIps = dcount(IPAddress), BlockedSignins = countif(ResultType == \"50053\"), TotalSignins = count() by AutonomousSystemNumber\n | extend BlockRatio = 1.00 * BlockedSignins / TotalSignins\n | where BlockRatio >= BLOCK_THRESHOLD\n | distinct AutonomousSystemNumber;\n// Retrieve IP addresses from these high block rate ASNs\nlet ASNIPs = SigninLogs\n | where TimeGenerated > lookback\n | where AutonomousSystemNumber in (HighBlockRateASNs)\n | distinct IPAddress, AutonomousSystemNumber;\n// OfficeActivity Query: File activities from identified ASN IPs\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated between(starttime .. endtime)\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | where ClientIP in (ASNIPs)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by ClientIP\n | extend IP_0_Address = ClientIP;\n// EnrichedMicrosoft365AuditLogs Query: File activities from identified ASN IPs\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | where ClientIp in (ASNIPs)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by ClientIp\n | extend IP_0_Address = ClientIp;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by ClientIP;\n// Final Output\nCombinedEvents\n | project StartTime, EndTime, RecentFileActivities, IP_0_Address\n | order by StartTime desc\n", "version": 2, "tags": [ { @@ -4272,10 +4249,10 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('huntingQueryObject16')._huntingQuerycontentId16]", "contentKind": "HuntingQuery", - "displayName": "SharePointFileOperation via previously unseen IPs", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject16')._huntingQuerycontentId16,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject16')._huntingQuerycontentId16,'-', '2.0.1')))]", - "version": "2.0.1" + "displayName": "GSA Enriched Office 365 - SharePointFileOperation via previously unseen IPs", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject16')._huntingQuerycontentId16,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject16')._huntingQuerycontentId16,'-', '2.0.2')))]", + "version": "2.0.2" } }, { @@ -4301,9 +4278,9 @@ "location": "[parameters('workspace-location')]", "properties": { "eTag": "*", - "displayName": "SharePointFileOperation via devices with previously unseen user agents", + "displayName": "GSA Enriched Office 365 -SharePointFileOperation via devices with previously unseen user agents", "category": "Hunting Queries", - "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = starttime - 14d;\nlet MINIMUM_BLOCKS = 10;\nlet SUCCESS_THRESHOLD = 0.2;\nlet HistoricalActivity = \n SigninLogs\n | where TimeGenerated > lookback\n | where isnotempty(ClientAppUsed)\n | summarize SuccessfulSignins = countif(ResultType == \"0\"), BlockedSignins = countif(ResultType == \"50053\") by ClientAppUsed\n | extend SuccessBlockRatio = 1.00 * SuccessfulSignins / BlockedSignins\n | where SuccessBlockRatio < SUCCESS_THRESHOLD\n | where BlockedSignins > MINIMUM_BLOCKS;\nEnrichedMicrosoft365AuditLogs\n| where TimeGenerated between (starttime .. endtime)\n| where RecordType == \"SharePointFileOperation\"\n| where Operation in (\"FileDownloaded\", \"FileUploaded\")\n| extend ClientAppUsed = tostring(parse_json(AdditionalProperties).UserAgent)\n| extend SiteUrl = tostring(parse_json(AdditionalProperties).SiteUrl)\n| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by ClientAppUsed, UserId, ClientIp, SiteUrl\n| join kind=innerunique (HistoricalActivity) on ClientAppUsed\n| project-away ClientAppUsed1\n| extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n| extend IP_0_Address = ClientIp\n| extend Account_0_Name = AccountName\n| extend Account_0_UPNSuffix = AccountUPNSuffix\n| extend URL_0_Url = SiteUrl\n", + "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = starttime - 14d;\nlet MINIMUM_BLOCKS = 10;\nlet SUCCESS_THRESHOLD = 0.2;\n// Identify user agents or client apps with a low success-to-block ratio in Sign-in Logs\nlet HistoricalActivity = SigninLogs\n | where TimeGenerated > lookback\n | where isnotempty(UserAgent)\n | summarize SuccessfulSignins = countif(ResultType == \"0\"), BlockedSignins = countif(ResultType == \"50053\") by UserAgent\n | extend SuccessBlockRatio = 1.00 * SuccessfulSignins / BlockedSignins\n | where SuccessBlockRatio < SUCCESS_THRESHOLD\n | where BlockedSignins > MINIMUM_BLOCKS;\n// OfficeActivity Query: File operations by matching user agents\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated between (starttime .. endtime)\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by UserAgent, UserId, ClientIP, Site_Url\n | join kind=innerunique (HistoricalActivity) on UserAgent\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIP, Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, URL_0_Url = Site_Url;\n// EnrichedMicrosoft365AuditLogs Query: File operations by matching client apps (UserAgent)\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) // Ensure matching with UserAgent column\n | extend SiteUrl = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by UserAgent, UserId, ClientIp, SiteUrl\n | join kind=innerunique (HistoricalActivity) on UserAgent\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIp, Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, URL_0_Url = SiteUrl;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by UserId, ClientIP;\n// Final Output\nCombinedEvents\n | project StartTime, EndTime, RecentFileActivities, IP_0_Address, Account_0_Name, Account_0_UPNSuffix, URL_0_Url\n | order by StartTime desc;\n", "version": 2, "tags": [ { @@ -4357,10 +4334,10 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('huntingQueryObject17')._huntingQuerycontentId17]", "contentKind": "HuntingQuery", - "displayName": "SharePointFileOperation via devices with previously unseen user agents", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject17')._huntingQuerycontentId17,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject17')._huntingQuerycontentId17,'-', '2.0.1')))]", - "version": "2.0.1" + "displayName": "GSA Enriched Office 365 -SharePointFileOperation via devices with previously unseen user agents", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject17')._huntingQuerycontentId17,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject17')._huntingQuerycontentId17,'-', '2.0.2')))]", + "version": "2.0.2" } }, { @@ -4386,9 +4363,9 @@ "location": "[parameters('workspace-location')]", "properties": { "eTag": "*", - "displayName": "Non-owner mailbox login activity", + "displayName": "GSA Enriched Office 365 - Non-owner mailbox login activity", "category": "Hunting Queries", - "query": "EnrichedMicrosoft365AuditLogs\n| where Workload == \"Exchange\"\n| where Operation == \"MailboxLogin\"\n| extend Logon_Type = tostring(parse_json(tostring(AdditionalProperties)).LogonType)\n| extend MailboxOwnerUPN = tostring(parse_json(tostring(AdditionalProperties)).MailboxOwnerUPN)\n| where Logon_Type != \"Owner\"\n| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by Operation, UserType, UserId, MailboxOwnerUPN, Logon_Type, ClientIp\n| extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n| extend IP_0_Address = ClientIp\n| extend Account_0_Name = AccountName\n| extend Account_0_UPNSuffix = AccountUPNSuffix\n", + "query": "let starttime = todatetime('{{StartTimeISO}}');\n let endtime = todatetime('{{EndTimeISO}}');\n // Enriched Logs Query for Mailbox Logins (non-owner)\n let EnrichedMailboxLogins = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where Workload == \"Exchange\"\n | where Operation == \"MailboxLogin\"\n | extend Logon_Type = tostring(parse_json(tostring(AdditionalProperties)).LogonType)\n | extend MailboxOwnerUPN = tostring(parse_json(tostring(AdditionalProperties)).MailboxOwnerUPN)\n | where Logon_Type != \"Owner\"\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by Operation, UserType, UserId, MailboxOwnerUPN, Logon_Type, ClientIp\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIp\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n // Office Activity Query for Mailbox Logins (non-owner)\n let OfficeMailboxLogins = OfficeActivity\n | where TimeGenerated between (starttime .. endtime)\n | where OfficeWorkload == \"Exchange\"\n | where Operation == \"MailboxLogin\" and Logon_Type != \"Owner\"\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by Operation, OrganizationName, UserType, UserId, MailboxOwnerUPN, Logon_Type, ClientIP\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIP\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n // Combine both results\n let CombinedMailboxLogins = EnrichedMailboxLogins\n | union OfficeMailboxLogins\n | summarize arg_min(StartTime, *) by UserId, MailboxOwnerUPN, Logon_Type;\n // Final output\n CombinedMailboxLogins\n | project StartTime, EndTime, Operation, UserId, MailboxOwnerUPN, Logon_Type, Account_0_Name, Account_0_UPNSuffix, IP_0_Address\n | order by StartTime desc\n", "version": 2, "tags": [ { @@ -4442,7 +4419,7 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('huntingQueryObject18')._huntingQuerycontentId18]", "contentKind": "HuntingQuery", - "displayName": "Non-owner mailbox login activity", + "displayName": "GSA Enriched Office 365 - Non-owner mailbox login activity", "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject18')._huntingQuerycontentId18,'-', '2.0.1')))]", "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject18')._huntingQuerycontentId18,'-', '2.0.1')))]", "version": "2.0.1" @@ -4471,9 +4448,9 @@ "location": "[parameters('workspace-location')]", "properties": { "eTag": "*", - "displayName": "PowerShell or non-browser mailbox login activity", + "displayName": "GSA Enriched Office 365 - PowerShell or non-browser mailbox login activity", "category": "Hunting Queries", - "query": "EnrichedMicrosoft365AuditLogs\n| where Workload == \"Exchange\" and Operation == \"MailboxLogin\"\n| extend ClientApplication = tostring(parse_json(AdditionalProperties).ClientInfoString)\n| where ClientApplication == \"Client=Microsoft.Exchange.Powershell; Microsoft WinRM Client\"\n| extend TenantName = tostring(parse_json(AdditionalProperties).TenantName)\n| extend MailboxOwner = tostring(parse_json(AdditionalProperties).MailboxOwnerUPN)\n| extend LogonType = tostring(parse_json(AdditionalProperties).LogonType)\n| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by Operation, TenantName, UserType, UserId, MailboxOwner, LogonType, ClientApplication\n| extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), UserId)\n| extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '')\n| extend AccountName = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[1]), AccountName)\n| extend AccountNTDomain = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[0]), '')\n", + "query": "let starttime = todatetime('{{StartTimeISO}}');\n let endtime = todatetime('{{EndTimeISO}}');\n // EnrichedMicrosoft365AuditLogs query\n let EnrichedMailboxLogin = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where Workload == \"Exchange\" and Operation == \"MailboxLogin\"\n | extend ClientApplication = tostring(parse_json(AdditionalProperties).ClientInfoString)\n | where ClientApplication == \"Client=Microsoft.Exchange.Powershell; Microsoft WinRM Client\"\n | extend TenantName = tostring(parse_json(AdditionalProperties).TenantName)\n | extend MailboxOwner = tostring(parse_json(AdditionalProperties).MailboxOwnerUPN)\n | extend LogonType = tostring(parse_json(AdditionalProperties).LogonType)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Count = count() by Operation, TenantName, UserType, UserId, MailboxOwner, LogonType, ClientApplication\n | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), UserId)\n | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '')\n | extend AccountName = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[1]), AccountName)\n | extend AccountNTDomain = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[0]), '');\n // OfficeActivity query\n let OfficeMailboxLogin = OfficeActivity\n | where TimeGenerated between (starttime .. endtime)\n | where OfficeWorkload == \"Exchange\" and Operation == \"MailboxLogin\"\n | where ClientInfoString == \"Client=Microsoft.Exchange.Powershell; Microsoft WinRM Client\"\n | extend LogonType = \"Unknown\" // If LogonType does not exist, create a placeholder\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Count = count() by Operation, OrganizationName, UserType, UserId, MailboxOwnerUPN, LogonType, ClientInfoString\n | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), UserId)\n | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '')\n | extend AccountName = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[1]), AccountName)\n | extend AccountNTDomain = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[0]), '');\n // Combine Enriched and Office queries\n let CombinedMailboxLogin = EnrichedMailboxLogin\n | union OfficeMailboxLogin\n | summarize arg_min(StartTime, *) by UserId, Operation\n | project StartTime, EndTime, Operation, TenantName, OrganizationName, UserType, UserId, MailboxOwner, LogonType, ClientApplication, ClientInfoString, Count, AccountName, AccountUPNSuffix, AccountNTDomain;\n // Final output\n CombinedMailboxLogin\n | order by StartTime desc;\n", "version": 2, "tags": [ { @@ -4527,10 +4504,10 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('huntingQueryObject19')._huntingQuerycontentId19]", "contentKind": "HuntingQuery", - "displayName": "PowerShell or non-browser mailbox login activity", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject19')._huntingQuerycontentId19,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject19')._huntingQuerycontentId19,'-', '2.0.1')))]", - "version": "2.0.1" + "displayName": "GSA Enriched Office 365 - PowerShell or non-browser mailbox login activity", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject19')._huntingQuerycontentId19,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject19')._huntingQuerycontentId19,'-', '2.0.2')))]", + "version": "2.0.2" } }, { @@ -4556,9 +4533,9 @@ "location": "[parameters('workspace-location')]", "properties": { "eTag": "*", - "displayName": "SharePoint File Operation via Client IP with Previously Unseen User Agents", + "displayName": "GSA Enriched Office 365 - SharePoint File Operation via Client IP with Previously Unseen User Agents", "category": "Hunting Queries", - "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = starttime - 14d;\nlet historicalUA = EnrichedMicrosoft365AuditLogs\n| where RecordType == \"SharePointFileOperation\"\n| where Operation in (\"FileDownloaded\", \"FileUploaded\")\n| where TimeGenerated between(lookback..starttime)\n| extend ClientApplication = tostring(parse_json(AdditionalProperties).UserAgent)\n| summarize by ClientIp, ClientApplication;\nlet recentUA = EnrichedMicrosoft365AuditLogs\n| where RecordType == \"SharePointFileOperation\"\n| where Operation in (\"FileDownloaded\", \"FileUploaded\")\n| where TimeGenerated between(starttime..endtime)\n| extend ClientApplication = tostring(parse_json(AdditionalProperties).UserAgent)\n| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by ClientIp, ClientApplication;\nrecentUA | join kind=leftanti (\n historicalUA\n) on ClientIp, ClientApplication\n// Some EnrichedMicrosoft365AuditLogs records do not contain ClientIp information - exclude these for fewer results\n| where not(isempty(ClientIp))\n| extend IP_0_Address = ClientIp\n", + "query": "let starttime = todatetime('{{StartTimeISO}}');\n let endtime = todatetime('{{EndTimeISO}}');\n let lookback = starttime - 14d;\n // Historical user agents in EnrichedMicrosoft365AuditLogs\n let historicalUA_Enriched = EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | where TimeGenerated between (lookback .. starttime)\n | extend ClientApplication = tostring(parse_json(AdditionalProperties).UserAgent)\n | summarize by ClientIp, ClientApplication;\n // Recent user agents in EnrichedMicrosoft365AuditLogs\n let recentUA_Enriched = EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | where TimeGenerated between (starttime .. endtime)\n | extend ClientApplication = tostring(parse_json(AdditionalProperties).UserAgent)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by ClientIp, ClientApplication;\n // Combine historical and recent user agents from EnrichedMicrosoft365AuditLogs\n let Enriched_UA = recentUA_Enriched\n | join kind=leftanti (historicalUA_Enriched) on ClientIp, ClientApplication\n | where not(isempty(ClientIp))\n | extend IP_0_Address = ClientIp;\n // Historical user agents in OfficeActivity\n let historicalUA_Office = OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | where TimeGenerated between (lookback .. starttime)\n | summarize by ClientIP, UserAgent;\n // Recent user agents in OfficeActivity\n let recentUA_Office = OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | where TimeGenerated between (starttime .. endtime)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by ClientIP, UserAgent;\n // Combine historical and recent user agents from OfficeActivity\n let Office_UA = recentUA_Office\n | join kind=leftanti (historicalUA_Office) on ClientIP, UserAgent\n | where not(isempty(ClientIP))\n | extend IP_0_Address = ClientIP;\n // Final combined result\n Enriched_UA\n | union Office_UA\n | project StartTime, EndTime, ClientIp, ClientApplication, IP_0_Address;\n", "version": 2, "tags": [ { @@ -4612,10 +4589,10 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('huntingQueryObject20')._huntingQuerycontentId20]", "contentKind": "HuntingQuery", - "displayName": "SharePoint File Operation via Client IP with Previously Unseen User Agents", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject20')._huntingQuerycontentId20,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject20')._huntingQuerycontentId20,'-', '2.0.1')))]", - "version": "2.0.1" + "displayName": "GSA Enriched Office 365 - SharePoint File Operation via Client IP with Previously Unseen User Agents", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject20')._huntingQuerycontentId20,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject20')._huntingQuerycontentId20,'-', '2.0.2')))]", + "version": "2.0.2" } }, { @@ -4641,9 +4618,9 @@ "location": "[parameters('workspace-location')]", "properties": { "eTag": "*", - "displayName": "Multiple Users Email Forwarded to Same Destination", + "displayName": "GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination", "category": "Hunting Queries", - "query": "let queryfrequency = 1d;\nlet queryperiod = 7d;\nEnrichedMicrosoft365AuditLogs\n| where TimeGenerated > ago(queryperiod)\n| where Workload == \"Exchange\"\n| where AdditionalProperties has_any (\"ForwardTo\", \"RedirectTo\", \"ForwardingSmtpAddress\")\n| mv-apply DynamicParameters = todynamic(AdditionalProperties) on (summarize ParsedParameters = make_bag(bag_pack(tostring(DynamicParameters.Name), DynamicParameters.Value)))\n| evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source')\n| extend DestinationMailAddress = tolower(case(\n isnotempty(column_ifexists(\"ForwardTo\", \"\")), column_ifexists(\"ForwardTo\", \"\"),\n isnotempty(column_ifexists(\"RedirectTo\", \"\")), column_ifexists(\"RedirectTo\", \"\"),\n isnotempty(column_ifexists(\"ForwardingSmtpAddress\", \"\")), trim_start(@\"smtp:\", column_ifexists(\"ForwardingSmtpAddress\", \"\")),\n \"\"))\n| where isnotempty(DestinationMailAddress)\n| mv-expand split(DestinationMailAddress, \";\")\n| extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIp)[0]\n| extend ClientIp = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1])\n| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIp\n| where DistinctUserCount > 1 and EndTime > ago(queryfrequency)\n| mv-expand UserId to typeof(string)\n| extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n", + "query": "let queryfrequency = 1d;\nlet queryperiod = 7d;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated > ago(queryperiod)\n | where Workload == \"Exchange\"\n | where AdditionalProperties has_any (\"ForwardTo\", \"RedirectTo\", \"ForwardingSmtpAddress\")\n | mv-apply DynamicParameters = todynamic(AdditionalProperties) on (\n summarize ParsedParameters = make_bag(bag_pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source')\n | extend DestinationMailAddress = tolower(case(\n isnotempty(column_ifexists(\"ForwardTo\", \"\")), column_ifexists(\"ForwardTo\", \"\"),\n isnotempty(column_ifexists(\"RedirectTo\", \"\")), column_ifexists(\"RedirectTo\", \"\"),\n isnotempty(column_ifexists(\"ForwardingSmtpAddress\", \"\")), trim_start(@\"smtp:\", column_ifexists(\"ForwardingSmtpAddress\", \"\")),\n \"\"\n ))\n | where isnotempty(DestinationMailAddress)\n | mv-expand split(DestinationMailAddress, \";\")\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIp)[0]\n | extend ClientIp = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1])\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIp\n | where DistinctUserCount > 1 and EndTime > ago(queryfrequency)\n | mv-expand UserId to typeof(string)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated > ago(queryperiod)\n | where OfficeWorkload =~ \"Exchange\"\n | where Parameters has_any (\"ForwardTo\", \"RedirectTo\", \"ForwardingSmtpAddress\")\n | mv-apply DynamicParameters = todynamic(Parameters) on (\n summarize ParsedParameters = make_bag(bag_pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source')\n | extend DestinationMailAddress = tolower(case(\n isnotempty(column_ifexists(\"ForwardTo\", \"\")), column_ifexists(\"ForwardTo\", \"\"),\n isnotempty(column_ifexists(\"RedirectTo\", \"\")), column_ifexists(\"RedirectTo\", \"\"),\n isnotempty(column_ifexists(\"ForwardingSmtpAddress\", \"\")), trim_start(@\"smtp:\", column_ifexists(\"ForwardingSmtpAddress\", \"\")),\n \"\"\n ))\n | where isnotempty(DestinationMailAddress)\n | mv-expand split(DestinationMailAddress, \";\")\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIP)[0]\n | extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1])\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP\n | where DistinctUserCount > 1 and EndTime > ago(queryfrequency)\n | mv-expand UserId to typeof(string)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, IP_0_Address = ClientIP;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by DestinationMailAddress, ClientIp;\n// Final Output\nCombinedEvents\n | project StartTime, EndTime, DestinationMailAddress, ClientIp, Ports, UserId, AccountName, AccountUPNSuffix\n | order by StartTime desc\n", "version": 2, "tags": [ { @@ -4697,10 +4674,10 @@ "contentSchemaVersion": "3.0.0", "contentId": "[variables('huntingQueryObject21')._huntingQuerycontentId21]", "contentKind": "HuntingQuery", - "displayName": "Multiple Users Email Forwarded to Same Destination", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject21')._huntingQuerycontentId21,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject21')._huntingQuerycontentId21,'-', '2.0.1')))]", - "version": "2.0.1" + "displayName": "GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject21')._huntingQuerycontentId21,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject21')._huntingQuerycontentId21,'-', '2.0.2')))]", + "version": "2.0.2" } }, { From c02f23865fa0ac6bc158b389aa612fde71247236 Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Sun, 6 Oct 2024 10:04:15 +0300 Subject: [PATCH 15/27] workbooks update --- .../Workbooks/GSAM365EnrichedEvents.json | 822 +++++++++++++++--- .../Workbooks/GSANetworkTraffic.json | 442 +++++++++- 2 files changed, 1097 insertions(+), 167 deletions(-) diff --git a/Solutions/Global Secure Access/Workbooks/GSAM365EnrichedEvents.json b/Solutions/Global Secure Access/Workbooks/GSAM365EnrichedEvents.json index 461ba0feb5..39b4440aa1 100644 --- a/Solutions/Global Secure Access/Workbooks/GSAM365EnrichedEvents.json +++ b/Solutions/Global Secure Access/Workbooks/GSAM365EnrichedEvents.json @@ -4,15 +4,17 @@ { "type": 1, "content": { - "json": "## Traffic Logs workbook\n---\n\nLog information in the dashboard is limited to 30 days." + "json": "## Enriched Microsoft 365 logs Workbook (Preview)\n---\n\nThe enriched Microsoft 365 logs provide information about Microsoft 365 workloads, so you can review network data and security events relevant to Microsoft 365 apps." }, - "name": "text - 1" + "name": "text - 2" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", - "crossComponentResources": [], + "crossComponentResources": [ + "value::all" + ], "parameters": [ { "id": "ff8b2a55-1849-4848-acf8-eab5452e9f10", @@ -20,24 +22,25 @@ "name": "LogAnalyticWorkspace", "label": "Log Analytic Workspace", "type": 5, - "description": "The log analytic workspace in which to execute the queries", + "description": "The Log Analytic Workspace In Which To Execute The Queries", "isRequired": true, "query": "resources\r\n| where type == \"microsoft.operationalinsights/workspaces\"\r\n| project id", + "crossComponentResources": [ + "value::all" + ], "typeSettings": { "resourceTypeFilter": { "microsoft.operationalinsights/workspaces": true }, - "additionalResourceOptions": [ - "value::1" - ], + "additionalResourceOptions": [], "showDefault": false }, "timeContext": { "durationMs": 86400000 }, - "defaultValue": "value::1", "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources" + "resourceType": "microsoft.resourcegraph/resources", + "value": null }, { "id": "f15f34d8-8e2d-4c39-8dee-be2f979c86a8", @@ -106,7 +109,10 @@ "multiSelect": true, "quote": "'", "delimiter": ",", - "query": "EnrichedMicrosoft365AuditLogsDemos_CL\r\n| summarize Count = count() by UserId_s\r\n| order by Count desc, UserId_s asc\r\n| project Value = UserId_s, Label = strcat(UserId_s, ' - ', Count, ' Logs'), Selected = false", + "query": "EnrichedMicrosoft365AuditLogs\r\n| summarize Count = count() by UserId\r\n| order by Count desc, UserId asc\r\n| project Value = UserId, Label = strcat(UserId, ' - ', Count, ' Logs'), Selected = false", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "typeSettings": { "limitSelectTo": 20, "additionalResourceOptions": [ @@ -128,7 +134,7 @@ ], "style": "pills", "queryType": 1, - "resourceType": "microsoft.operationalinsights/workspaces" + "resourceType": "microsoft.resourcegraph/resources" }, "name": "parameters - 15" }, @@ -137,21 +143,38 @@ "content": { "version": "LinkItem/1.0", "style": "tabs", + "tabStyle": "bigger", "links": [ { - "id": "2b2cd1be-9d25-412c-8444-f005c4789b55", - "cellValue": "tabSel", + "id": "e841bafb-6437-4d29-84ac-ba16c5a6d901", + "cellValue": "selTab", "linkTarget": "parameter", "linkLabel": "Overview", "subTarget": "Overview", "style": "link" }, { - "id": "cc3e67f2-f20f-4430-8dee-d0773b90d9ce", - "cellValue": "tabSel", + "id": "ac5f2082-50bc-4739-bdf2-20c93b613671", + "cellValue": "selTab", + "linkTarget": "parameter", + "linkLabel": "SharePoint/OneDrive Insights", + "subTarget": "Threat", + "style": "link" + }, + { + "id": "dc2778e7-739b-44ba-9ae4-c81901277f57", + "cellValue": "selTab", "linkTarget": "parameter", - "linkLabel": "All Traffic", - "subTarget": "AllTraffic", + "linkLabel": "Exchange Online Insights", + "subTarget": "ThreatEXO", + "style": "link" + }, + { + "id": "666111e2-54ff-4fa4-a648-11a5c8c0235b", + "cellValue": "selTab", + "linkTarget": "parameter", + "linkLabel": "Teams Insights", + "subTarget": "ThreatTeams", "style": "link" } ] @@ -159,77 +182,179 @@ "name": "links - 7" }, { - "type": 3, + "type": 12, "content": { - "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| project \r\n Timestamp = createdDateTime_t,\r\n User = userPrincipalName_s,\r\n SourceIP = SourceIP,\r\n DestinationIP = destinationIp_s,\r\n DestinationPort = destinationPort_d,\r\n Action = action_s,\r\n PolicyName = policyName_s,\r\n TransportProtocol = transportProtocol_s,\r\n TrafficType = trafficType_s,\r\n DestinationURL = destinationUrl_s,\r\n ReceivedBytes = receivedBytes_d,\r\n SentBytes = sentBytes_d,\r\n DeviceOS = deviceOperatingSystem_s,\r\n PolicyRuleID = policyRuleId_s\r\n| order by Timestamp desc", - "size": 3, - "showAnalytics": true, - "title": "Log", - "timeContextFromParameter": "TimeRange", - "showExportToExcel": true, - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "gridSettings": { - "rowLimit": 1000, - "filter": true - } + "version": "NotebookGroup/1.0", + "groupType": "editable", + "items": [ + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| where Workload in (\"Exchange\")\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\r\n| extend \r\n Country = tostring(GeoInfo.country), \r\n State = tostring(GeoInfo.state), \r\n City = tostring(GeoInfo.city), \r\n Latitude = tostring(GeoInfo.latitude), \r\n Longitude = tostring(GeoInfo.longitude)\r\n| project \r\n UserId, \r\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\r\n Latitude, \r\n Longitude, \r\n City, \r\n State, \r\n Country\r\n| summarize Count = count() by City, State, Country\r\n", + "size": 0, + "title": "Access By Location", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "visualization": "map", + "mapSettings": { + "locInfo": "CountryRegion", + "locInfoColumn": "Country", + "latitude": "Latitude", + "longitude": "Longitude", + "sizeSettings": "Count", + "sizeAggregation": "Sum", + "labelSettings": "Country", + "legendMetric": "Count", + "legendAggregation": "Sum", + "itemColorSettings": { + "nodeColorField": "Count", + "colorAggregation": "Sum", + "type": "heatmap", + "heatmapPalette": "turquoise" + } + } + }, + "customWidth": "50", + "name": "query - 0" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| where Workload in (\"Exchange\")\r\n| summarize [\"Count\"] = count() by [\"Source IP\"] = SourceIp, [\"Device Operating System\"] = DeviceOperatingSystem\r\n| order by [\"Count\"] desc\r\n", + "size": 0, + "title": "Access By Location And OS", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "customWidth": "50", + "name": "query - 1" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where Operation in (\"Set-AdminAuditLogConfig\", \"Set-OrganizationConfig\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", + "size": 0, + "title": "Audit Of Critical Configuration Changes", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "customWidth": "50", + "name": "query - 2" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where Operation in (\"Set-TransportRule\", \"Set-AtpPolicyForO365\", \"Set-MalwareFilterRule\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", + "size": 0, + "title": "Changes In Email Security Policies", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "customWidth": "50", + "name": "query - 3" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where Operation in (\"Add-RoleGroupMember\", \"Remove-ManagementRoleAssignment\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", + "size": 0, + "title": "Monitoring Role Group Membership Changes", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "customWidth": "50", + "name": "query - 4" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where Operation in (\"New-TenantAllowBlockListSpoofitems\", \"Remove-TenantAllowBlockListSpoofitems\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", + "size": 0, + "title": "Spoofing Settings Management Activities", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "customWidth": "50", + "name": "query - 5" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where Operation in (\"New-SafeAttachmentPolicy\", \"Set-SafeAttachmentPolicy\", \"Set-SafeAttachmentRule\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", + "size": 0, + "title": "Safe Attachments Policy Changes", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "customWidth": "50", + "name": "query - 6" + } + ] }, "conditionalVisibility": { - "parameterName": "tabSel", + "parameterName": "selTab", "comparison": "isEqualTo", - "value": "AllTraffic" + "value": "ThreatEXO" }, - "name": "query - 6" + "name": "group - 18" }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "// Unique Users\nNetworkAccessDemo_CL\n| extend GeoInfo = geo_info_from_ip_address(SourceIP) // Extend each row with geolocation info\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\n| project SourceIP, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\n| summarize UniqueUsers=dcount(Country)\n| extend snapshot = \"Total Locations\"\n| project col1 = UniqueUsers, snapshot\n\n// Union with Unique Devices\n| union (\n NetworkAccessDemo_CL\n | where userPrincipalName_s in ({Users}) or '*' in ({Users})\n | extend BytesInGB = todouble(sentBytes_d + receivedBytes_d) / (1024 * 1024 * 1024) // Convert bytes to gigabytes\n | summarize TotalBytesGB = sum(BytesInGB)\n | extend snapshot = \"Total Bytes (GB)\"\n | project col1 = tolong(TotalBytesGB), snapshot\n)\n\n// Union with Total Internet Access\n| union (\n NetworkAccessDemo_CL\n | where userPrincipalName_s in ({Users}) or '*' in ({Users})\n | summarize TotalTransactions = count()\n | extend snapshot = \"Total Transactions\"\n | project col1 = TotalTransactions, snapshot\n)\n\n// Union with Total Private Access\n// Order by Snapshot for consistent tile ordering on dashboard\n| order by snapshot", - "size": 4, - "timeContextFromParameter": "TimeRange", + "query": "EnrichedMicrosoft365AuditLogs | where Workload == \"Exchange\" | where Operation in (\"Add-MailboxFolderPermission\", \"Add-RoleGroupMember\", \"New-TransportRule\", \"Remove-ManagementRoleAssignment\", \"Set-TransportRule\") | summarize Count = count(), Users = makeset(UserId) by Operation | order by Count desc", + "size": 0, + "title": "Permission changes and security policy updates", + "timeContext": { + "durationMs": 86400000 + }, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", - "visualization": "tiles", - "tileSettings": { - "titleContent": { - "columnMatch": "snapshot", - "formatter": 1 - }, - "leftContent": { - "columnMatch": "col1", - "formatter": 12, - "formatOptions": { - "palette": "auto" - } - }, - "showBorder": true, - "size": "auto" - }, - "mapSettings": { - "locInfo": "LatLong", - "sizeSettings": "ExistingClients", - "sizeAggregation": "Sum", - "legendMetric": "ExistingClients", - "legendAggregation": "Sum", - "itemColorSettings": { - "type": "heatmap", - "colorAggregation": "Sum", - "nodeColorField": "ExistingClients", - "heatmapPalette": "greenRed" - } - }, - "textSettings": { - "style": "bignumber" - } + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] }, "conditionalVisibility": { - "parameterName": "tabSel", + "parameterName": "seltab", "comparison": "isEqualTo", - "value": "Overview" + "value": "ThreatEXO" }, - "name": "query - 2" + "name": "query - 13" }, { "type": 12, @@ -241,24 +366,62 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| extend BytesInGB = todouble(sentBytes_d + receivedBytes_d) / (1024 * 1024 * 1024) // Convert bytes to gigabytes\r\n| summarize TotalBytesGB = sum(BytesInGB) by bin(createdDateTime_t, 1h), trafficType_s\r\n| order by bin(createdDateTime_t, 1h) asc, trafficType_s asc\r\n| project createdDateTime_t, trafficType_s, TotalBytesGB\r\n", - "size": 2, - "title": "Usage over Time (GB)", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| where Workload in (\"Teams\")\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\r\n| extend \r\n Country = tostring(GeoInfo.country), \r\n State = tostring(GeoInfo.state), \r\n City = tostring(GeoInfo.city), \r\n Latitude = tostring(GeoInfo.latitude), \r\n Longitude = tostring(GeoInfo.longitude)\r\n| project \r\n UserId, \r\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\r\n Latitude, \r\n Longitude, \r\n City, \r\n State, \r\n Country\r\n| summarize Count = count() by City, State, Country\r\n", + "size": 0, + "title": "Access By Location", "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", - "visualization": "barchart" - }, - "conditionalVisibility": { - "parameterName": "tabSel", - "comparison": "isEqualTo", - "value": "Overview" + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "visualization": "map", + "mapSettings": { + "locInfo": "CountryRegion", + "locInfoColumn": "Country", + "latitude": "Latitude", + "longitude": "Longitude", + "sizeSettings": "Count", + "sizeAggregation": "Sum", + "labelSettings": "Country", + "legendMetric": "Count", + "legendAggregation": "Sum", + "itemColorSettings": { + "nodeColorField": "Count", + "colorAggregation": "Sum", + "type": "heatmap", + "heatmapPalette": "turquoise" + } + } }, + "customWidth": "50", "name": "query - 0" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| where Workload in (\"Teams\")\r\n| summarize [\"Count\"] = count() by [\"Source IP\"] = SourceIp, [\"Device Operating System\"] = DeviceOperatingSystem\r\n| order by [\"Count\"] desc\r\n", + "size": 0, + "title": "Access By Location And OS", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "customWidth": "50", + "name": "query - 1" } ] }, - "name": "group - 5" + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "ThreatTeams" + }, + "name": "group - 10" }, { "type": 12, @@ -270,12 +433,15 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIP) // Extend each row with geolocation info\r\n| project createdDateTime_t, SourceIP, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\r\n| where Country != \"\"\r\n| summarize Count = count() by City, State, Country\r\n", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| where Workload in (\"OneDrive\", \"SharePoint\",\"SPO/OneDrive\")\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\r\n| extend \r\n Country = tostring(GeoInfo.country), \r\n State = tostring(GeoInfo.state), \r\n City = tostring(GeoInfo.city), \r\n Latitude = tostring(GeoInfo.latitude), \r\n Longitude = tostring(GeoInfo.longitude)\r\n| project \r\n UserId, \r\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\r\n Latitude, \r\n Longitude, \r\n City, \r\n State, \r\n Country\r\n| summarize Count = count() by City, State, Country\r\n", "size": 0, - "title": "Locations", + "title": "Access By Location", "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "visualization": "map", "mapSettings": { "locInfo": "CountryRegion", @@ -286,6 +452,7 @@ "sizeAggregation": "Sum", "labelSettings": "Country", "legendMetric": "Country", + "numberOfMetrics": 19, "legendAggregation": "Count", "itemColorSettings": { "nodeColorField": "Count", @@ -296,88 +463,190 @@ } }, "customWidth": "50", - "name": "query - 0" + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Threat" + }, + "name": "query - 19" }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where tolower(action_s) == \"allow\" and destinationWebCategory_displayName_s != '' // Filter for allowed traffic\r\n| extend firstCategory = tostring(split(destinationWebCategory_displayName_s, ',')[0]) // Split and get the first category\r\n| summarize Count = count() by firstCategory\r\n| top 10 by Count\r\n", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| where Workload in (\"OneDrive\", \"SharePoint\", \"SPO/OneDrive\")\r\n| summarize [\"Event Count\"] = count() by [\"Source IP\"] = SourceIp, [\"Device Operating System\"] = DeviceOperatingSystem\r\n| order by [\"Event Count\"] desc\r\n", "size": 2, - "title": "Top Allowed Web Categories", + "title": "Access By Location And OS", "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", - "visualization": "piechart" + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "gridSettings": { + "filter": true + } }, "customWidth": "50", - "name": "query - 7" - }, + "name": "query - 20" + } + ] + }, + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Threat" + }, + "name": "group - 20", + "styleSettings": { + "showBorder": true + } + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where Workload == \"Teams\"\r\n| where Operation in (\"MemberAdded\", \"MemberRemoved\", \"TeamDeleted\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", + "size": 0, + "title": "Changes In Team Memberships", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "ThreatTeams" + }, + "customWidth": "50", + "name": "query - 11" + }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "items": [ { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where tolower(action_s) == \"block\" and destinationFQDN_s != '' // Filter for allowed traffic\r\n| summarize Count = count() by destinationFQDN_s\r\n| top 100 by Count", + "query": "let RiskyExtensions = dynamic([\"exe\", \"msi\", \"bat\", \"cmd\", \"com\", \"scr\", \"pif\", \"ps1\", \"vbs\", \"js\", \"jse\", \"wsf\", \"docm\", \"xlsm\", \"pptm\", \"dll\", \"ocx\", \"cpl\", \"app\", \"vb\", \"reg\", \"inf\", \"hta\"]);\r\n\r\nEnrichedMicrosoft365AuditLogs\r\n| where Workload in (\"SPO/OneDrive\")\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| extend [\"File Extension\"] = tostring(parse_json(AdditionalProperties).SourceFileExtension)\r\n| where [\"File Extension\"] in (RiskyExtensions)\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, [\"File Extension\"], Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], [\"File Extension\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], [\"File Extension\"], Operation, Count\r\n", "size": 0, - "title": "Top Blocked Destinations", + "title": "Risky File Operations", "timeContextFromParameter": "TimeRange", "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "gridSettings": { + "formatters": [ + { + "columnMatch": "OperationCount", + "formatter": 4, + "formatOptions": { + "palette": "blue" + } + } + ] + } }, "customWidth": "50", - "name": "query - 5" + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Threat" + }, + "name": "query - 1" }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where tolower(action_s) == \"block\" and destinationWebCategory_displayName_s != '' // Filter for blocked traffic\r\n| extend firstCategory = tostring(split(destinationWebCategory_displayName_s, ',')[0]) // Split and get the first category\r\n| summarize Count = count() by firstCategory\r\n| top 10 by Count\r\n", - "size": 3, - "title": "Top Blocked Web Categories", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where Workload in (\"OneDrive\", \"SharePoint\", \"SPO/OneDrive\")\r\n| where Operation in (\"FileRecycled\", \"FileDownloaded\", \"FileUploaded\", \"FileCreated\", \"File Modified\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", + "size": 0, + "title": "Bulk File Events", "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", - "visualization": "piechart" + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "gridSettings": { + "formatters": [ + { + "columnMatch": "OperationCount", + "formatter": 4, + "formatOptions": { + "palette": "blue" + } + } + ] + } }, "customWidth": "50", - "name": "query - 6" + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Threat" + }, + "name": "query - 8" }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where sentBytes_d > 0\r\n| where tolower(action_s) != \"block\" \r\n| summarize Count = count() , Sent = sum(sentBytes_d), Recived = sum(receivedBytes_d), Total = sum(receivedBytes_d+ sentBytes_d) by destinationFQDN_s\r\n| order by Count desc\r\n", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where Workload in (\"SPO/OneDrive\")\r\n| where Operation == \"FileDeletedFirstStageRecycleBin\"\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by bin(Timestamp, 1h), [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n| where Count > 1\r\n", "size": 0, - "title": "Top Allowed Destinations", + "title": " Bulk File Deletion Operations", "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "gridSettings": { "formatters": [ { - "columnMatch": "Count", - "formatter": 4, - "formatOptions": { - "palette": "magenta" - } - }, - { - "columnMatch": "Recived", - "formatter": 4, + "columnMatch": "FileDeletions", + "formatter": 8, "formatOptions": { - "palette": "turquoise" - } - }, - { - "columnMatch": "Total", - "formatter": 4, - "formatOptions": { - "palette": "pink" + "palette": "blue" } - }, + } + ] + } + }, + "customWidth": "50", + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Threat" + }, + "name": "query - 3" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "let baselinePeriod = 30d;\r\nlet detectionWindow = 1h;\r\nlet downloadThreshold = 5; // Threshold of downloads indicating potential exfiltration\r\n\r\nEnrichedMicrosoft365AuditLogs\r\n| where TimeGenerated >= ago(baselinePeriod)\r\n| where Workload in (\"OneDrive\", \"SharePoint\", \"SPO/OneDrive\")\r\n| where Operation == \"FileDownloaded\"\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by bin(Timestamp, detectionWindow), [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", + "size": 0, + "title": "Bulk File Download", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "gridSettings": { + "formatters": [ { - "columnMatch": "Sent", - "formatter": 4, + "columnMatch": "DownloadCount", + "formatter": 8, "formatOptions": { "palette": "blue" } @@ -386,33 +655,328 @@ } }, "customWidth": "50", + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Threat" + }, + "name": "query - 4" + } + ] + }, + "name": "group - 9" + }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "items": [ + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\r\n| extend \r\n Country = tostring(GeoInfo.country), \r\n State = tostring(GeoInfo.state), \r\n City = tostring(GeoInfo.city), \r\n Latitude = tostring(GeoInfo.latitude), \r\n Longitude = tostring(GeoInfo.longitude)\r\n| project \r\n UserId, \r\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\r\n Latitude, \r\n Longitude, \r\n City, \r\n State, \r\n Country\r\n | where tostring(Country) != \"\"\r\n| summarize Count = count() by City, State, Country\r\n\r\n\r\n", + "size": 0, + "title": "Access By Location", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "visualization": "map", + "mapSettings": { + "locInfo": "CountryRegion", + "locInfoColumn": "Country", + "latitude": "Latitude", + "longitude": "Longitude", + "sizeSettings": "Count", + "sizeAggregation": "Sum", + "minSize": 20, + "labelSettings": "Country", + "legendMetric": "Country", + "numberOfMetrics": 8, + "legendAggregation": "Count", + "itemColorSettings": { + "nodeColorField": "Count", + "colorAggregation": "Sum", + "type": "heatmap", + "heatmapPalette": "turquoise" + } + } + }, + "customWidth": "50", + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Overview" + }, + "name": "query - 5" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| summarize [\"Event Count\"] = count() by [\"Time Generated\"] = TimeGenerated, [\"User\"] = UserId, [\"Source IP\"] = SourceIp, [\"Device Operating System\"] = DeviceOperatingSystem, [\"Workload\"] = Workload\r\n| order by [\"Time Generated\"] desc\r\n", + "size": 0, + "title": "Access By Location And OS", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "gridSettings": { + "rowLimit": 1000, + "filter": true + } + }, + "customWidth": "50", + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Overview" + }, "name": "query - 1" + } + ] + }, + "name": "group - 19" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "let data = EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| project \r\n Operation, \r\n UserId, \r\n Workload, \r\n SourceIp, \r\n DeviceId, \r\n TimeGenerated, \r\n Details = pack_all(),\r\n OS = tostring(DeviceOperatingSystem)\r\n| extend Workload = tostring(Workload)\r\n| extend WorkloadStatus = case(\r\n Workload == \"Exchange\", \"Exchange\",\r\n Workload == \"Teams\", \"Teams\",\r\n Workload == \"SharePoint\", \"SharePoint\",\r\n \"Other\"\r\n);\r\n\r\nlet appData = data\r\n| summarize \r\n TotalCount = count(), \r\n ExchangeCount = countif(Workload == \"Exchange\"), \r\n TeamsCount = countif(Workload == \"Teams\"), \r\n SharePointCount = countif(Workload == \"SharePoint\"), \r\n OtherCount = countif(Workload == \"Other\") \r\n by UserId\r\n| where UserId != ''\r\n| join kind=inner (\r\n data\r\n | make-series Trend = count() default = 0 on TimeGenerated in range({TimeRange:start}, {TimeRange:end}, {TimeRange:grain}) by UserId\r\n | project-away TimeGenerated\r\n ) on UserId\r\n| order by TotalCount desc, UserId asc\r\n| project UserId, TotalCount, ExchangeCount, TeamsCount, SharePointCount, OtherCount, Trend\r\n| serialize Id = row_number();\r\n\r\ndata\r\n| summarize \r\n TotalCount = count(), \r\n ExchangeCount = countif(Workload == \"Exchange\"), \r\n TeamsCount = countif(Workload == \"Teams\"), \r\n SharePointCount = countif(Workload == \"SharePoint\"), \r\n OtherCount = countif(Workload == \"Other\") \r\n by UserId, SourceIp = tostring(SourceIp)\r\n| join kind=inner (\r\n data\r\n | make-series Trend = count() default = 0 on TimeGenerated in range({TimeRange:start}, {TimeRange:end}, {TimeRange:grain}) by UserId, SourceIp\r\n | project-away TimeGenerated\r\n ) on UserId, SourceIp\r\n| order by TotalCount desc, UserId asc\r\n| project UserId, SourceIp, TotalCount, ExchangeCount, TeamsCount, SharePointCount, OtherCount, Trend\r\n| serialize Id = row_number(1000000)\r\n| join kind=inner (appData) on UserId\r\n| project Id, Name = SourceIp, Type = 'Client IP', ['Events Count'] = TotalCount, Trend, ['Exchange Events'] = ExchangeCount, ['Teams Events'] = TeamsCount, ['SharePoint Events'] = SharePointCount, ['Other Count'] = OtherCount, ParentId = Id1\r\n| union (\r\n appData \r\n | project Id, Name = UserId, Type = 'Operating System', ['Events Count'] = TotalCount, Trend, ['Exchange Events'] = ExchangeCount, ['Teams Events'] = TeamsCount, ['SharePoint Events'] = SharePointCount, ['Other Count'] = OtherCount, ParentId = -1\r\n )\r\n| order by ['Events Count'] desc, Name asc\r\n", + "size": 2, + "title": "Activity Log", + "timeContextFromParameter": "TimeRange", + "showRefreshButton": true, + "showExportToExcel": true, + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "gridSettings": { + "formatters": [ + { + "columnMatch": "Id", + "formatter": 5 + }, + { + "columnMatch": "Type", + "formatter": 5 + }, + { + "columnMatch": "Trend", + "formatter": 9, + "formatOptions": { + "palette": "blue" + } + }, + { + "columnMatch": "Other Count", + "formatter": 5 + }, + { + "columnMatch": "ParentId", + "formatter": 5 + }, + { + "columnMatch": "Operation Count", + "formatter": 8, + "formatOptions": { + "palette": "blue" + } + }, + { + "columnMatch": "Exchange Count", + "formatter": 8, + "formatOptions": { + "min": 0, + "palette": "blue" + } + }, + { + "columnMatch": "Teams Count", + "formatter": 8, + "formatOptions": { + "min": 0, + "palette": "purple" + } + }, + { + "columnMatch": "SharePoint Count", + "formatter": 8, + "formatOptions": { + "min": 0, + "palette": "turquoise" + } + }, + { + "columnMatch": "Details", + "formatter": 5, + "formatOptions": { + "linkTarget": "GenericDetails", + "linkIsContextBlade": true + } + } + ], + "rowLimit": 1000, + "filter": true, + "hierarchySettings": { + "idColumn": "Id", + "parentColumn": "ParentId", + "treeType": 0, + "expanderColumn": "Name" + } + } + }, + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Overview" + }, + "name": "query - 6" + }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "items": [ + { + "type": 1, + "content": { + "json": "
\r\nšŸ’” _Click on a segment of the pie chart to explore more details_" + }, + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Overview" + }, + "name": "text - 2" }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where transportProtocol_s != ''\r\n| summarize Count = count() by toupper(transportProtocol_s)\r\n| top 10 by Count\r\n", - "size": 2, - "title": "Protocol Distburion", + "query": "EnrichedMicrosoft365AuditLogs\r\n| extend \r\n OS = coalesce(DeviceOperatingSystem, \"Unknown OS\"),\r\n OSVersion = coalesce(tostring(DeviceOperatingSystemVersion), \"Unknown Version\")\r\n| summarize DeviceCount = count() by OS, OSVersion\r\n| order by DeviceCount desc\r\n", + "size": 3, + "title": "Devices Accessing M365", "timeContextFromParameter": "TimeRange", + "exportParameterName": "Parampie", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "visualization": "piechart" }, "customWidth": "50", - "name": "query - 3" + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Overview" + }, + "name": "query - 6" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| extend \r\n [\"Operating System\"] = coalesce(DeviceOperatingSystem, \"Unknown OS\"),\r\n [\"OS Version\"] = coalesce(tostring(DeviceOperatingSystemVersion), \"Unknown Version\"),\r\n [\"Device ID\"] = coalesce(tostring(DeviceId), \"Unknown DeviceId\")\r\n| where [\"Operating System\"] == dynamic({Parampie}).label\r\n| summarize [\"Device Count\"] = count() by [\"Operating System\"], [\"OS Version\"], [\"Device ID\"]\r\n| order by [\"Device Count\"] desc\r\n", + "size": 2, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "gridSettings": { + "formatters": [ + { + "columnMatch": "$gen_group", + "formatter": 1 + } + ], + "hierarchySettings": { + "treeType": 1, + "groupBy": [ + "OperatingSystem", + "OSVersion" + ], + "expandTopLevel": false + } + } + }, + "customWidth": "50", + "conditionalVisibilities": [ + { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Overview" + }, + { + "parameterName": "Parampie", + "comparison": "isNotEqualTo" + } + ], + "name": "query - 1" } ] }, + "name": "group - 19" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "let BusinessHoursStart = 8; // 8 AM\r\nlet BusinessHoursEnd = 18; // 6 PM\r\n\r\nEnrichedMicrosoft365AuditLogs \r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| extend HourOfDay = hourofday(TimeGenerated)\r\n| where HourOfDay < BusinessHoursStart or HourOfDay > BusinessHoursEnd\r\n| summarize [\"Off-Hour Activities\"] = count() by [\"User ID\"] = UserId, [\"Date\"] = bin(TimeGenerated, 1d), [\"Operation\"]\r\n| order by [\"Off-Hour Activities\"] desc\r\n", + "size": 0, + "title": "Activity Outside Standard Working Hours (8:00 - 18:00)", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Overview" + }, + "name": "query - 14" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\n| summarize Count = count() by bin(TimeGenerated, 1h)\n| order by TimeGenerated asc\n", + "size": 0, + "title": "Microsoft 365 Transactions", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "visualization": "unstackedbar" + }, "conditionalVisibility": { - "parameterName": "tabSel", + "parameterName": "selTab", "comparison": "isEqualTo", "value": "Overview" }, - "name": "group - 4" + "name": "query - 2" } ], - "fallbackResourceIds": [], + "fallbackResourceIds": [ + "Global Secure Access" + ], + "fromTemplateId": "community-Workbooks/Global Secure Access/Microsoft Entra Internet Access/Enriched Microsoft 365 logs", "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json" -} +} \ No newline at end of file diff --git a/Solutions/Global Secure Access/Workbooks/GSANetworkTraffic.json b/Solutions/Global Secure Access/Workbooks/GSANetworkTraffic.json index 46e0e43e6f..92c9d904ea 100644 --- a/Solutions/Global Secure Access/Workbooks/GSANetworkTraffic.json +++ b/Solutions/Global Secure Access/Workbooks/GSANetworkTraffic.json @@ -4,16 +4,16 @@ { "type": 1, "content": { - "json": "## Traffic Logs workbook\n---\n\nLog information in the dashboard is limited to 30 days." + "json": "## Network Traffic Insights Workbook (Preview)\n---\nInformation in the dashboard is based on log data\n\n\n" }, - "name": "text - 0" + "name": "text - 2" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "crossComponentResources": [ - "" + "value::all" ], "parameters": [ { @@ -22,24 +22,25 @@ "name": "LogAnalyticWorkspace", "label": "Log Analytic Workspace", "type": 5, - "description": "The log analytic workspace in which to execute the queries", + "description": "The Log Analytic Workspace In Which To Execute The Queries", "isRequired": true, "query": "resources\r\n| where type == \"microsoft.operationalinsights/workspaces\"\r\n| project id", + "crossComponentResources": [ + "value::all" + ], "typeSettings": { "resourceTypeFilter": { "microsoft.operationalinsights/workspaces": true }, - "additionalResourceOptions": [ - "value::1" - ], + "additionalResourceOptions": [], "showDefault": false }, "timeContext": { - "durationMs": 86400000 + "durationMs": 2592000000 }, - "defaultValue": "value::1", "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources" + "resourceType": "microsoft.resourcegraph/resources", + "value": null }, { "id": "f15f34d8-8e2d-4c39-8dee-be2f979c86a8", @@ -96,7 +97,7 @@ "durationMs": 86400000 }, "value": { - "durationMs": 2592000000 + "durationMs": 604800000 } }, { @@ -108,7 +109,10 @@ "multiSelect": true, "quote": "'", "delimiter": ",", - "query": "EnrichedMicrosoft365AuditLogsDemos_CL\r\n| summarize Count = count() by UserId_s\r\n| order by Count desc, UserId_s asc\r\n| project Value = UserId_s, Label = strcat(UserId_s, ' - ', Count, ' Logs'), Selected = false", + "query": "NetworkAccessTraffic\r\n| summarize Count = count() by UserPrincipalName\r\n| order by Count desc, UserPrincipalName asc\r\n| project Value = UserPrincipalName, Label = strcat(UserPrincipalName, ' - ', Count, ' Logs'), Selected = false", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "typeSettings": { "limitSelectTo": 20, "additionalResourceOptions": [ @@ -126,10 +130,41 @@ "value": [ "value::all" ] + }, + { + "id": "527af4d2-3089-4aa4-9fbb-48ec697db20d", + "version": "KqlParameterItem/1.0", + "name": "WebCategories", + "label": "Web Categories", + "type": 2, + "multiSelect": true, + "quote": "'", + "delimiter": ",", + "query": "NetworkAccessTraffic\r\n| extend firstCategory = tostring(split(DestinationWebCategories, ',')[0]) // Split and get the first category\r\n| summarize Count = count() by firstCategory\r\n| order by Count desc, firstCategory asc\r\n| project Value = firstCategory, Label = strcat(firstCategory, ' - ', Count, ' Logs')", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "typeSettings": { + "additionalResourceOptions": [ + "value::all" + ], + "selectAllValue": "*", + "showDefault": false + }, + "timeContext": { + "durationMs": 604800000 + }, + "timeContextFromParameter": "TimeRange", + "defaultValue": "value::all", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "value": [ + "value::all" + ] } ], "style": "pills", - "queryType": 1, + "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces" }, "name": "parameters - 15" @@ -155,23 +190,286 @@ "linkLabel": "All Traffic", "subTarget": "AllTraffic", "style": "link" + }, + { + "id": "5ae54b5a-ac7b-4b7a-a1e1-1e574625caa3", + "cellValue": "tabSel", + "linkTarget": "parameter", + "linkLabel": "URL Lookup", + "subTarget": "URLLookup", + "style": "link" + }, + { + "id": "68c566f8-957e-4a3f-8b66-730fc24135fb", + "cellValue": "tabSel", + "linkTarget": "parameter", + "linkLabel": "Security Insights", + "subTarget": "Security", + "style": "link" } ] }, "name": "links - 7" }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "items": [ + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "let NetworkAccessTrafficData = NetworkAccessTraffic\r\n| where SourceIp != \"\" and isnotempty(SourceIp)\r\n| summarize arg_max(TimeGenerated, *) by SourceIp, TenantId;\r\n\r\nlet SigninLogsData = SigninLogs\r\n| where IPAddress != \"\" and isnotempty(IPAddress)\r\n| summarize arg_max(TimeGenerated, *) by IPAddress, TenantId, UserId, CorrelationId;\r\n\r\nSigninLogsData\r\n| join kind=leftanti (\r\n NetworkAccessTrafficData\r\n | where SourceIp != \"\" and isnotempty(SourceIp)\r\n) on $left.IPAddress == $right.SourceIp and $left.TenantId == $right.TenantId\r\n| project TimeGenerated, IPAddress, UserId, UserPrincipalName, AppDisplayName, DeviceDetail.deviceId\r\n", + "size": 0, + "title": "Sign-ins Outside Global Secure Access", + "timeContextFromParameter": "TimeRange", + "showExportToExcel": true, + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "gridSettings": { + "rowLimit": 1000, + "filter": true + } + }, + "name": "query - 0" + } + ] + }, + "conditionalVisibility": { + "parameterName": "tabSel", + "comparison": "isEqualTo", + "value": "Security" + }, + "name": "group - 10" + }, + { + "type": 1, + "content": { + "json": "šŸ’” Type the URL for detailed analysis" + }, + "conditionalVisibility": { + "parameterName": "tabSel", + "comparison": "isEqualTo", + "value": "URLLookup" + }, + "name": "text - 10" + }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "items": [ + { + "type": 9, + "content": { + "version": "KqlParameterItem/1.0", + "parameters": [ + { + "id": "ec8c38c8-3064-4921-8dcd-a69d3895599b", + "version": "KqlParameterItem/1.0", + "name": "URL", + "type": 1, + "typeSettings": { + "isSearchBox": true + }, + "timeContext": { + "durationMs": 86400000 + } + } + ], + "style": "above", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces" + }, + "conditionalVisibility": { + "parameterName": "tabSel", + "comparison": "isEqualTo", + "value": "URLLookup" + }, + "name": "parameters - 9" + }, + { + "type": 1, + "content": { + "json": "# Web Categories" + }, + "name": "text - 11" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "let CategoryData = NetworkAccessTraffic \r\n| where DestinationFqdn contains \"{URL}\" // Filters logs based on the provided URL parameter\r\n| extend CategoriesList = split(DestinationWebCategories, \",\") // Split categories into a list\r\n| mv-expand Category = CategoriesList // Expand the list into individual rows\r\n| extend Category = trim_start(\" \", trim_end(\" \", tostring(Category))) // Trim leading and trailing spaces\r\n| extend Category = iff(TrafficType == \"microsoft365\", \"Microsoft M365\", Category) // Set category to \"Microsoft M365\" if TrafficType is \"microsoft365\"\r\n| summarize UniqueCategories = make_set(Category); // Create a set of unique categories\r\n\r\nCategoryData\r\n| extend \r\n PrimaryCategory = iff(array_length(UniqueCategories) > 0, tostring(UniqueCategories[0]), \"None\")\r\n| project \r\n col1 = PrimaryCategory,\r\n snapshot = \"Primary Category\"\r\n\r\n| union (\r\n CategoryData\r\n | extend \r\n SecondaryCategory = iff(array_length(UniqueCategories) > 1, tostring(UniqueCategories[1]), \"None\")\r\n | project \r\n col1 = SecondaryCategory,\r\n snapshot = \"Secondary Category\"\r\n)\r\n\r\n| order by snapshot asc\r\n", + "size": 4, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "visualization": "tiles", + "tileSettings": { + "titleContent": { + "columnMatch": "snapshot" + }, + "leftContent": { + "columnMatch": "col1", + "numberFormat": { + "unit": 17, + "options": { + "style": "decimal", + "maximumFractionDigits": 2 + } + } + }, + "showBorder": true, + "size": "auto" + } + }, + "name": "query - 17" + }, + { + "type": 1, + "content": { + "json": "# Traffic Access Details" + }, + "name": "text - 15" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "let NetworkData = NetworkAccessTraffic\r\n| where DestinationFqdn contains \"{URL}\"; // Filters logs based on the provided URL parameter\r\n\r\nNetworkData\r\n| summarize \r\n UniqueUsers = dcount(UserPrincipalName),\r\n UniqueDevices = dcount(DeviceId), \r\n TotalAllow = countif(Action == \"Allow\"),\r\n TotalBlock = countif(Action == \"Block\")\r\n| project \r\n col1 = UniqueUsers, \r\n snapshot = \"Unique Users\"\r\n| union (NetworkData\r\n | summarize UniqueDevices = dcount(DeviceId)\r\n | project col1 = UniqueDevices, snapshot = \"Unique Devices\")\r\n| union (NetworkData\r\n | summarize TotalAllow = countif(Action == \"Allow\")\r\n | project col1 = TotalAllow, snapshot = \"Total Allow\")\r\n| union (NetworkData\r\n | summarize TotalBlock = countif(Action == \"Block\")\r\n | project col1 = TotalBlock, snapshot = \"Total Block\")\r\n| order by snapshot asc", + "size": 4, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "visualization": "tiles", + "tileSettings": { + "titleContent": { + "columnMatch": "snapshot", + "formatter": 1 + }, + "leftContent": { + "columnMatch": "col1", + "formatter": 12, + "formatOptions": { + "palette": "auto" + }, + "numberFormat": { + "unit": 17, + "options": { + "style": "decimal", + "maximumFractionDigits": 2, + "maximumSignificantDigits": 3 + } + } + }, + "showBorder": true + } + }, + "name": "query - 14" + }, + { + "type": 1, + "content": { + "json": "# Bandwidth Usage" + }, + "name": "text - 16" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "let NetworkData = NetworkAccessTraffic\r\n| where DestinationFqdn contains \"{URL}\"; // Filters logs based on the provided URL parameter\r\n\r\nNetworkData\r\n| summarize \r\n TotalSent = sum(SentBytes),\r\n TotalReceived = sum(ReceivedBytes),\r\n TotalBandwidth = sum(SentBytes + ReceivedBytes)\r\n| extend \r\n TotalSentMB = round(TotalSent / 1024.0 / 1024.0, 2),\r\n TotalReceivedMB = round(TotalReceived / 1024.0 / 1024.0, 2),\r\n TotalBandwidthMB = round(TotalBandwidth / 1024.0 / 1024.0, 2)\r\n| project \r\n TotalSentMB,\r\n TotalReceivedMB,\r\n TotalBandwidthMB\r\n| extend dummy = 1\r\n| project-away dummy\r\n| mv-expand \r\n Column = pack_array(\"TotalSentMB\", \"TotalReceivedMB\", \"TotalBandwidthMB\"),\r\n Value = pack_array(TotalSentMB, TotalReceivedMB, TotalBandwidthMB)\r\n| extend \r\n snapshot = case(\r\n Column == \"TotalSentMB\", \"Total Sent (MB)\",\r\n Column == \"TotalReceivedMB\", \"Total Received (MB)\",\r\n Column == \"TotalBandwidthMB\", \"Total Bandwidth (MB)\",\r\n \"Unknown\"\r\n ),\r\n col1 = iff(Value < 0.01, 0.00, Value)\r\n| project-away Column, Value\r\n| order by snapshot asc", + "size": 4, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "visualization": "tiles", + "tileSettings": { + "titleContent": { + "columnMatch": "snapshot", + "formatter": 1 + }, + "leftContent": { + "columnMatch": "col1", + "formatter": 12, + "formatOptions": { + "palette": "auto" + }, + "numberFormat": { + "unit": 17, + "options": { + "style": "decimal", + "maximumFractionDigits": 2, + "maximumSignificantDigits": 3 + } + } + }, + "showBorder": true + } + }, + "name": "query - 17" + }, + { + "type": 1, + "content": { + "json": "# Traffic Logs (filtered)" + }, + "name": "text - 12" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| where DestinationFqdn contains \"{URL}\"\r\n| project \r\n Timestamp = TimeGenerated,\r\n User = UserPrincipalName,\r\n [\"Source IP\"] = SourceIp,\r\n [\"Destination IP\"] = DestinationIp,\r\n [\"Destination Port\"] = DestinationPort,\r\n [\"Destination FQDN\"] = DestinationFqdn,\r\n Action = case(\r\n tolower(Action) == \"allow\", \"šŸŸ¢ Allow\", \r\n tolower(Action) == \"block\", \"šŸ”“ Block\", \r\n tolower(Action) // This returns the action in lowercase if it doesn't match \"allow\" or \"block\"\r\n ),\r\n [\"Policy Name\"] = PolicyName,\r\n [\"Policy Rule ID\"] = PolicyRuleId,\r\n [\"Received Bytes\"] = ReceivedBytes,\r\n [\"Sent Bytes\"] = SentBytes,\r\n [\"Device OS\"] = DeviceOperatingSystem \r\n| order by Timestamp desc\r\n", + "size": 0, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "name": "query - 13" + } + ] + }, + "conditionalVisibility": { + "parameterName": "tabSel", + "comparison": "isEqualTo", + "value": "URLLookup" + }, + "name": "group - URL Lookup" + }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| project \r\n Timestamp = createdDateTime_t,\r\n User = userPrincipalName_s,\r\n SourceIP = SourceIP,\r\n DestinationIP = destinationIp_s,\r\n DestinationPort = destinationPort_d,\r\n Action = action_s,\r\n PolicyName = policyName_s,\r\n TransportProtocol = transportProtocol_s,\r\n TrafficType = trafficType_s,\r\n DestinationURL = destinationUrl_s,\r\n ReceivedBytes = receivedBytes_d,\r\n SentBytes = sentBytes_d,\r\n DeviceOS = deviceOperatingSystem_s,\r\n PolicyRuleID = policyRuleId_s\r\n| order by Timestamp desc", + "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| project \r\n Timestamp = TimeGenerated,\r\n User = UserPrincipalName,\r\n [\"Source IP\"] = SourceIp,\r\n [\"Destination IP\"] = DestinationIp,\r\n [\"Destination Port\"] = DestinationPort,\r\n [\"Destination FQDN\"] = DestinationFqdn,\r\n Action = case(\r\n tolower(Action) == \"allow\", \"šŸŸ¢ Allow\", \r\n tolower(Action) == \"block\", \"šŸ”“ Block\", \r\n tolower(Action) // This returns the action in lowercase if it doesn't match \"allow\" or \"block\"\r\n ),\r\n [\"Policy Name\"] = PolicyName,\r\n [\"Web Category\"] = DestinationWebCategories,\r\n [\"Transport Protocol\"] = TransportProtocol,\r\n [\"Traffic Type\"] = TrafficType,\r\n [\"Received Bytes\"] = ReceivedBytes,\r\n [\"Sent Bytes\"] = SentBytes,\r\n [\"Device OS\"] = DeviceOperatingSystem,\r\n [\"Policy Rule ID\"] = PolicyRuleId\r\n| order by Timestamp desc\r\n", "size": 3, - "showAnalytics": true, - "title": "Log", + "title": "Traffic Logs", "timeContextFromParameter": "TimeRange", + "showRefreshButton": true, "showExportToExcel": true, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "gridSettings": { "rowLimit": 1000, "filter": true @@ -188,11 +486,14 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "// Unique Users\nNetworkAccessDemo_CL\n| extend GeoInfo = geo_info_from_ip_address(SourceIP) // Extend each row with geolocation info\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\n| project SourceIP, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\n| summarize UniqueUsers=dcount(Country)\n| extend snapshot = \"Total Locations\"\n| project col1 = UniqueUsers, snapshot\n\n// Union with Unique Devices\n| union (\n NetworkAccessDemo_CL\n | where userPrincipalName_s in ({Users}) or '*' in ({Users})\n | extend BytesInGB = todouble(sentBytes_d + receivedBytes_d) / (1024 * 1024 * 1024) // Convert bytes to gigabytes\n | summarize TotalBytesGB = sum(BytesInGB)\n | extend snapshot = \"Total Bytes (GB)\"\n | project col1 = tolong(TotalBytesGB), snapshot\n)\n\n// Union with Total Internet Access\n| union (\n NetworkAccessDemo_CL\n | where userPrincipalName_s in ({Users}) or '*' in ({Users})\n | summarize TotalTransactions = count()\n | extend snapshot = \"Total Trasnacations\"\n | project col1 = TotalTransactions, snapshot\n)\n\n// Union with Total Private Access\n// Order by Snapshot for consistent tile ordering on dashboard\n| order by snapshot", + "query": "// Unique Users\nNetworkAccessTraffic\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Extend each row with geolocation info\n| project SourceIp, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\n| summarize UniqueUsers=dcount(Country)\n| extend snapshot = \"Total Locations\"\n| project col1 = UniqueUsers, snapshot\n\n// Union with Unique Devices\n| union (\n NetworkAccessTraffic\n | where UserPrincipalName in ({Users}) or '*' in ({Users})\n | extend BytesInGB = todouble(SentBytes + ReceivedBytes) / (1024 * 1024 * 1024) // Convert bytes to gigabytes\n | summarize TotalBytesGB = sum(BytesInGB)\n | extend snapshot = \"Total Bytes (GB)\"\n | project col1 = tolong(TotalBytesGB), snapshot\n)\n\n// Union with Total Internet Access\n| union (\n NetworkAccessTraffic\n | where UserPrincipalName in ({Users}) or '*' in ({Users})\n | summarize TotalTransactions = count()\n | extend snapshot = \"Total Transactions\"\n | project col1 = TotalTransactions, snapshot\n)\n\n// Union with Total Private Access\n// Order by Snapshot for consistent tile ordering on dashboard\n| order by snapshot", "size": 4, "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "visualization": "tiles", "tileSettings": { "titleContent": { @@ -243,12 +544,15 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| extend BytesInGB = todouble(sentBytes_d + receivedBytes_d) / (1024 * 1024 * 1024) // Convert bytes to gigabytes\r\n| summarize TotalBytesGB = sum(BytesInGB) by bin(createdDateTime_t, 1h), trafficType_s\r\n| order by bin(createdDateTime_t, 1h) asc, trafficType_s asc\r\n| project createdDateTime_t, trafficType_s, TotalBytesGB\r\n", + "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| extend BytesIn = todouble(SentBytes + ReceivedBytes) / (1024 * 1024) // Convert bytes to Mbytes\r\n| summarize TotalBytesMB = sum(BytesIn) by bin(TimeGenerated, 1h), TrafficType\r\n| order by bin(TimeGenerated, 1h) asc, TrafficType asc\r\n| project TimeGenerated, TrafficType, TotalBytesMB\r\n", "size": 2, - "title": "Usage over Time (GB)", + "title": "Usage Over Time (MB)", "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "visualization": "barchart" }, "conditionalVisibility": { @@ -272,12 +576,15 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIP) // Extend each row with geolocation info\r\n| project createdDateTime_t, SourceIP, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\r\n| where Country != \"\"\r\n| summarize Count = count() by City, State, Country\r\n\r\n", - "size": 0, + "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Extend each row with geolocation info\r\n| project TimeGenerated, SourceIp, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\r\n| where Country != \"\"\r\n| summarize Count = count() by City, State, Country\r\n\r\n", + "size": 3, "title": "Locations", "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "visualization": "map", "mapSettings": { "locInfo": "CountryRegion", @@ -297,19 +604,21 @@ } } }, - "customWidth": "50", "name": "query - 0" }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where tolower(action_s) == \"allow\" and destinationWebCategory_displayName_s != '' // Filter for allowed traffic\r\n| extend firstCategory = tostring(split(destinationWebCategory_displayName_s, ',')[0]) // Split and get the first category\r\n| summarize Count = count() by firstCategory\r\n| top 10 by Count\r\n", + "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| where tolower(Action) == \"allow\" and DestinationWebCategories != '' // Filter for allowed traffic\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| extend firstCategory = tostring(split(DestinationWebCategories, ',')[0]) // Split and get the first category\r\n| summarize Count = count() by firstCategory\r\n| top 10 by Count\r\n", "size": 2, "title": "Top Allowed Web Categories", "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "visualization": "piechart" }, "customWidth": "50", @@ -319,41 +628,79 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where tolower(action_s) == \"block\" and destinationFQDN_s != '' // Filter for allowed traffic\r\n| summarize Count = count() by destinationFQDN_s\r\n| top 100 by Count", - "size": 0, - "title": "Top Blocked Destinations", + "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| where tolower(Action) == \"block\" and DestinationWebCategories != '' // Filter for blocked traffic\r\n| extend firstCategory = tostring(split(DestinationWebCategories, ',')[0]) // Split and get the first category\r\n| summarize Count = count() by firstCategory\r\n| top 10 by Count\r\n", + "size": 3, + "title": "Top Blocked Web Categories", "timeContextFromParameter": "TimeRange", "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "visualization": "piechart" }, "customWidth": "50", - "name": "query - 5" + "name": "query - 6" }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where tolower(action_s) == \"block\" and destinationWebCategory_displayName_s != '' // Filter for blocked traffic\r\n| extend firstCategory = tostring(split(destinationWebCategory_displayName_s, ',')[0]) // Split and get the first category\r\n| summarize Count = count() by firstCategory\r\n| top 10 by Count\r\n", - "size": 3, - "title": "Top Blocked Web Categories", + "query": "NetworkAccessTraffic \r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| where tolower(Action) == \"block\" and DestinationFqdn != '' // Filter for blocked traffic with non-empty Destination FQDN\r\n| summarize Count = count() by [\"Destination FQDN\"] = DestinationFqdn, [\"Destination Web Categories\"] = DestinationWebCategories, [\"Policy Name\"] = PolicyName\r\n| order by Count\r\n", + "size": 0, + "title": "Top Blocked Destinations", "timeContextFromParameter": "TimeRange", + "showRefreshButton": true, + "showExportToExcel": true, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", - "visualization": "piechart" + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "gridSettings": { + "formatters": [ + { + "columnMatch": "Count", + "formatter": 4, + "formatOptions": { + "palette": "blue" + } + } + ], + "rowLimit": 1000, + "filter": true, + "sortBy": [ + { + "itemKey": "$gen_bar_Count_3", + "sortOrder": 2 + } + ] + }, + "sortBy": [ + { + "itemKey": "$gen_bar_Count_3", + "sortOrder": 2 + } + ] }, "customWidth": "50", - "name": "query - 6" + "name": "query - 5" }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where sentBytes_d > 0\r\n| where tolower(action_s) != \"block\" \r\n| summarize Count = count() , Sent = sum(sentBytes_d), Recived = sum(receivedBytes_d), Total = sum(receivedBytes_d+ sentBytes_d) by destinationFQDN_s\r\n| order by Count desc\r\n", + "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| where SentBytes > 0\r\n| where tolower(Action) != \"block\"\r\n| summarize \r\n Count = count(), \r\n [\"Sent Bytes\"] = sum(SentBytes), \r\n [\"Received Bytes\"] = sum(ReceivedBytes), \r\n [\"Total Bytes\"] = sum(ReceivedBytes + SentBytes) \r\n by [\"Destination FQDN\"] = DestinationFqdn\r\n| order by Count desc\r\n", "size": 0, "title": "Top Allowed Destinations", "timeContextFromParameter": "TimeRange", + "showRefreshButton": true, + "showExportToExcel": true, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "gridSettings": { "formatters": [ { @@ -384,8 +731,22 @@ "palette": "blue" } } + ], + "rowLimit": 1000, + "filter": true, + "sortBy": [ + { + "itemKey": "$gen_bar_Count_1", + "sortOrder": 2 + } ] - } + }, + "sortBy": [ + { + "itemKey": "$gen_bar_Count_1", + "sortOrder": 2 + } + ] }, "customWidth": "50", "name": "query - 1" @@ -394,12 +755,15 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where transportProtocol_s != ''\r\n| summarize Count = count() by toupper(transportProtocol_s)\r\n| top 10 by Count\r\n", + "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where TransportProtocol != ''\r\n| summarize Count = count() by toupper(TransportProtocol)\r\n| top 10 by Count\r\n", "size": 2, - "title": "Protocol Distburion", + "title": "Protocol Distribution", "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "visualization": "piechart" }, "customWidth": "50", @@ -415,7 +779,9 @@ "name": "group - 4" } ], - "fallbackResourceIds": [ + "fallbackResourceIds": [ + "Global Secure Access" ], + "fromTemplateId": "community-Workbooks/Global Secure Access/Common/Network Traffic Insights", "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json" } \ No newline at end of file From 70cd17e40be7a9eef97a8e509737a2b3b6f38704 Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Sun, 6 Oct 2024 10:46:22 +0300 Subject: [PATCH 16/27] updates --- ...repoint_file_transfer_above_threshold.yaml | 4 +- .../Global Secure Access/Package/3.0.0.zip | Bin 42645 -> 48426 bytes .../Package/mainTemplate.json | 12 +- .../Workbooks/GSAM365EnrichedEvents.json | 2 +- .../Workbooks/GSANetworkTraffic.json | 2 +- Workbooks/GSAM365EnrichedEvents.json | 822 +++++++++++++++--- Workbooks/GSANetworkTraffic.json | 442 +++++++++- Workbooks/WorkbooksMetadata.json | 4 +- 8 files changed, 1109 insertions(+), 179 deletions(-) diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml index 82243b34ae..f9a4994748 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml @@ -78,10 +78,10 @@ incidentConfiguration: groupingConfiguration: enabled: true reopenClosedIncident: false - lookbackDuration: 5h + lookbackDuration: PT5H matchingMethod: Selected groupByEntities: - - Account + - Account groupByAlertDetails: [] groupByCustomDetails: [] version: 2.0.7 diff --git a/Solutions/Global Secure Access/Package/3.0.0.zip b/Solutions/Global Secure Access/Package/3.0.0.zip index e33be3976441c5ae8cd76bcac237e6867da12c7c..6df173c9523aba092dc9c606146005040b430f97 100644 GIT binary patch delta 42377 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 NkOwKQ?d=Yj{{cRHSfKy_ delta 36536 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 e9U8RY)9S^)tnk~ 0\\r\\n| where tolower(action_s) != \\\"block\\\" \\r\\n| summarize Count = count() , Sent = sum(sentBytes_d), Recived = sum(receivedBytes_d), Total = sum(receivedBytes_d+ sentBytes_d) by destinationFQDN_s\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Top Allowed Destinations\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Count\",\"formatter\":4,\"formatOptions\":{\"palette\":\"magenta\"}},{\"columnMatch\":\"Recived\",\"formatter\":4,\"formatOptions\":{\"palette\":\"turquoise\"}},{\"columnMatch\":\"Total\",\"formatter\":4,\"formatOptions\":{\"palette\":\"pink\"}},{\"columnMatch\":\"Sent\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\"}}]}},\"customWidth\":\"50\",\"name\":\"query - 1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessDemo_CL\\r\\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\\r\\n| where transportProtocol_s != ''\\r\\n| summarize Count = count() by toupper(transportProtocol_s)\\r\\n| top 10 by Count\\r\\n\",\"size\":2,\"title\":\"Protocol Distburion\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\"},\"customWidth\":\"50\",\"name\":\"query - 3\"}]},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"group - 4\"}],\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", + "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"## Enriched Microsoft 365 logs Workbook (Preview)\\n---\\n\\nThe enriched Microsoft 365 logs provide information about Microsoft 365 workloads, so you can review network data and security events relevant to Microsoft 365 apps.\"},\"name\":\"text - 2\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"value::all\"],\"parameters\":[{\"id\":\"ff8b2a55-1849-4848-acf8-eab5452e9f10\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"LogAnalyticWorkspace\",\"label\":\"Log Analytic Workspace\",\"type\":5,\"description\":\"The Log Analytic Workspace In Which To Execute The Queries\",\"isRequired\":true,\"query\":\"resources\\r\\n| where type == \\\"microsoft.operationalinsights/workspaces\\\"\\r\\n| project id\",\"crossComponentResources\":[\"value::all\"],\"typeSettings\":{\"resourceTypeFilter\":{\"microsoft.operationalinsights/workspaces\":true},\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},{\"id\":\"f15f34d8-8e2d-4c39-8dee-be2f979c86a8\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TimeRange\",\"label\":\"Time Range\",\"type\":4,\"isRequired\":true,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":300000},{\"durationMs\":900000},{\"durationMs\":1800000},{\"durationMs\":3600000},{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000}],\"allowCustom\":true},\"timeContext\":{\"durationMs\":86400000},\"value\":{\"durationMs\":2592000000}},{\"id\":\"8bab511b-53b3-4220-9d1c-372345b06728\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Users\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| summarize Count = count() by UserId\\r\\n| order by Count desc, UserId asc\\r\\n| project Value = UserId, Label = strcat(UserId, ' - ', Count, ' Logs'), Selected = false\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"typeSettings\":{\"limitSelectTo\":20,\"additionalResourceOptions\":[\"value::all\"],\"selectAllValue\":\"*\",\"showDefault\":false},\"timeContext\":{\"durationMs\":0},\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":[\"value::all\"]}],\"style\":\"pills\",\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},\"name\":\"parameters - 15\"},{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"tabs\",\"tabStyle\":\"bigger\",\"links\":[{\"id\":\"e841bafb-6437-4d29-84ac-ba16c5a6d901\",\"cellValue\":\"selTab\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Overview\",\"subTarget\":\"Overview\",\"style\":\"link\"},{\"id\":\"ac5f2082-50bc-4739-bdf2-20c93b613671\",\"cellValue\":\"selTab\",\"linkTarget\":\"parameter\",\"linkLabel\":\"SharePoint/OneDrive Insights\",\"subTarget\":\"Threat\",\"style\":\"link\"},{\"id\":\"dc2778e7-739b-44ba-9ae4-c81901277f57\",\"cellValue\":\"selTab\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Exchange Online Insights\",\"subTarget\":\"ThreatEXO\",\"style\":\"link\"},{\"id\":\"666111e2-54ff-4fa4-a648-11a5c8c0235b\",\"cellValue\":\"selTab\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Teams Insights\",\"subTarget\":\"ThreatTeams\",\"style\":\"link\"}]},\"name\":\"links - 7\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| where Workload in (\\\"Exchange\\\")\\r\\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\\r\\n| extend \\r\\n Country = tostring(GeoInfo.country), \\r\\n State = tostring(GeoInfo.state), \\r\\n City = tostring(GeoInfo.city), \\r\\n Latitude = tostring(GeoInfo.latitude), \\r\\n Longitude = tostring(GeoInfo.longitude)\\r\\n| project \\r\\n UserId, \\r\\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\\r\\n Latitude, \\r\\n Longitude, \\r\\n City, \\r\\n State, \\r\\n Country\\r\\n| summarize Count = count() by City, State, Country\\r\\n\",\"size\":0,\"title\":\"Access By Location\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"Country\",\"latitude\":\"Latitude\",\"longitude\":\"Longitude\",\"sizeSettings\":\"Count\",\"sizeAggregation\":\"Sum\",\"labelSettings\":\"Country\",\"legendMetric\":\"Count\",\"legendAggregation\":\"Sum\",\"itemColorSettings\":{\"nodeColorField\":\"Count\",\"colorAggregation\":\"Sum\",\"type\":\"heatmap\",\"heatmapPalette\":\"turquoise\"}}},\"customWidth\":\"50\",\"name\":\"query - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| where Workload in (\\\"Exchange\\\")\\r\\n| summarize [\\\"Count\\\"] = count() by [\\\"Source IP\\\"] = SourceIp, [\\\"Device Operating System\\\"] = DeviceOperatingSystem\\r\\n| order by [\\\"Count\\\"] desc\\r\\n\",\"size\":0,\"title\":\"Access By Location And OS\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Operation in (\\\"Set-AdminAuditLogConfig\\\", \\\"Set-OrganizationConfig\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Audit Of Critical Configuration Changes\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Operation in (\\\"Set-TransportRule\\\", \\\"Set-AtpPolicyForO365\\\", \\\"Set-MalwareFilterRule\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Changes In Email Security Policies\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 3\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Operation in (\\\"Add-RoleGroupMember\\\", \\\"Remove-ManagementRoleAssignment\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Monitoring Role Group Membership Changes\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 4\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Operation in (\\\"New-TenantAllowBlockListSpoofitems\\\", \\\"Remove-TenantAllowBlockListSpoofitems\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Spoofing Settings Management Activities\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 5\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Operation in (\\\"New-SafeAttachmentPolicy\\\", \\\"Set-SafeAttachmentPolicy\\\", \\\"Set-SafeAttachmentRule\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Safe Attachments Policy Changes\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 6\"}]},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"ThreatEXO\"},\"name\":\"group - 18\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs | where Workload == \\\"Exchange\\\" | where Operation in (\\\"Add-MailboxFolderPermission\\\", \\\"Add-RoleGroupMember\\\", \\\"New-TransportRule\\\", \\\"Remove-ManagementRoleAssignment\\\", \\\"Set-TransportRule\\\") | summarize Count = count(), Users = makeset(UserId) by Operation | order by Count desc\",\"size\":0,\"title\":\"Permission changes and security policy updates\",\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"conditionalVisibility\":{\"parameterName\":\"seltab\",\"comparison\":\"isEqualTo\",\"value\":\"ThreatEXO\"},\"name\":\"query - 13\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| where Workload in (\\\"Teams\\\")\\r\\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\\r\\n| extend \\r\\n Country = tostring(GeoInfo.country), \\r\\n State = tostring(GeoInfo.state), \\r\\n City = tostring(GeoInfo.city), \\r\\n Latitude = tostring(GeoInfo.latitude), \\r\\n Longitude = tostring(GeoInfo.longitude)\\r\\n| project \\r\\n UserId, \\r\\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\\r\\n Latitude, \\r\\n Longitude, \\r\\n City, \\r\\n State, \\r\\n Country\\r\\n| summarize Count = count() by City, State, Country\\r\\n\",\"size\":0,\"title\":\"Access By Location\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"Country\",\"latitude\":\"Latitude\",\"longitude\":\"Longitude\",\"sizeSettings\":\"Count\",\"sizeAggregation\":\"Sum\",\"labelSettings\":\"Country\",\"legendMetric\":\"Count\",\"legendAggregation\":\"Sum\",\"itemColorSettings\":{\"nodeColorField\":\"Count\",\"colorAggregation\":\"Sum\",\"type\":\"heatmap\",\"heatmapPalette\":\"turquoise\"}}},\"customWidth\":\"50\",\"name\":\"query - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| where Workload in (\\\"Teams\\\")\\r\\n| summarize [\\\"Count\\\"] = count() by [\\\"Source IP\\\"] = SourceIp, [\\\"Device Operating System\\\"] = DeviceOperatingSystem\\r\\n| order by [\\\"Count\\\"] desc\\r\\n\",\"size\":0,\"title\":\"Access By Location And OS\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 1\"}]},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"ThreatTeams\"},\"name\":\"group - 10\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| where Workload in (\\\"OneDrive\\\", \\\"SharePoint\\\",\\\"SPO/OneDrive\\\")\\r\\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\\r\\n| extend \\r\\n Country = tostring(GeoInfo.country), \\r\\n State = tostring(GeoInfo.state), \\r\\n City = tostring(GeoInfo.city), \\r\\n Latitude = tostring(GeoInfo.latitude), \\r\\n Longitude = tostring(GeoInfo.longitude)\\r\\n| project \\r\\n UserId, \\r\\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\\r\\n Latitude, \\r\\n Longitude, \\r\\n City, \\r\\n State, \\r\\n Country\\r\\n| summarize Count = count() by City, State, Country\\r\\n\",\"size\":0,\"title\":\"Access By Location\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"Country\",\"latitude\":\"Latitude\",\"longitude\":\"Longitude\",\"sizeSettings\":\"Count\",\"sizeAggregation\":\"Sum\",\"labelSettings\":\"Country\",\"legendMetric\":\"Country\",\"numberOfMetrics\":19,\"legendAggregation\":\"Count\",\"itemColorSettings\":{\"nodeColorField\":\"Count\",\"colorAggregation\":\"Sum\",\"type\":\"heatmap\",\"heatmapPalette\":\"turquoise\"}}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Threat\"},\"name\":\"query - 19\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| where Workload in (\\\"OneDrive\\\", \\\"SharePoint\\\", \\\"SPO/OneDrive\\\")\\r\\n| summarize [\\\"Event Count\\\"] = count() by [\\\"Source IP\\\"] = SourceIp, [\\\"Device Operating System\\\"] = DeviceOperatingSystem\\r\\n| order by [\\\"Event Count\\\"] desc\\r\\n\",\"size\":2,\"title\":\"Access By Location And OS\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"filter\":true}},\"customWidth\":\"50\",\"name\":\"query - 20\"}]},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Threat\"},\"name\":\"group - 20\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Workload == \\\"Teams\\\"\\r\\n| where Operation in (\\\"MemberAdded\\\", \\\"MemberRemoved\\\", \\\"TeamDeleted\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Changes In Team Memberships\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"ThreatTeams\"},\"customWidth\":\"50\",\"name\":\"query - 11\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let RiskyExtensions = dynamic([\\\"exe\\\", \\\"msi\\\", \\\"bat\\\", \\\"cmd\\\", \\\"com\\\", \\\"scr\\\", \\\"pif\\\", \\\"ps1\\\", \\\"vbs\\\", \\\"js\\\", \\\"jse\\\", \\\"wsf\\\", \\\"docm\\\", \\\"xlsm\\\", \\\"pptm\\\", \\\"dll\\\", \\\"ocx\\\", \\\"cpl\\\", \\\"app\\\", \\\"vb\\\", \\\"reg\\\", \\\"inf\\\", \\\"hta\\\"]);\\r\\n\\r\\nEnrichedMicrosoft365AuditLogs\\r\\n| where Workload in (\\\"SPO/OneDrive\\\")\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| extend [\\\"File Extension\\\"] = tostring(parse_json(AdditionalProperties).SourceFileExtension)\\r\\n| where [\\\"File Extension\\\"] in (RiskyExtensions)\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, [\\\"File Extension\\\"], Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], [\\\"File Extension\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], [\\\"File Extension\\\"], Operation, Count\\r\\n\",\"size\":0,\"title\":\"Risky File Operations\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"OperationCount\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\"}}]}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Threat\"},\"name\":\"query - 1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Workload in (\\\"OneDrive\\\", \\\"SharePoint\\\", \\\"SPO/OneDrive\\\")\\r\\n| where Operation in (\\\"FileRecycled\\\", \\\"FileDownloaded\\\", \\\"FileUploaded\\\", \\\"FileCreated\\\", \\\"File Modified\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Bulk File Events\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"OperationCount\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\"}}]}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Threat\"},\"name\":\"query - 8\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Workload in (\\\"SPO/OneDrive\\\")\\r\\n| where Operation == \\\"FileDeletedFirstStageRecycleBin\\\"\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by bin(Timestamp, 1h), [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n| where Count > 1\\r\\n\",\"size\":0,\"title\":\" Bulk File Deletion Operations\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"FileDeletions\",\"formatter\":8,\"formatOptions\":{\"palette\":\"blue\"}}]}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Threat\"},\"name\":\"query - 3\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let baselinePeriod = 30d;\\r\\nlet detectionWindow = 1h;\\r\\nlet downloadThreshold = 5; // Threshold of downloads indicating potential exfiltration\\r\\n\\r\\nEnrichedMicrosoft365AuditLogs\\r\\n| where TimeGenerated >= ago(baselinePeriod)\\r\\n| where Workload in (\\\"OneDrive\\\", \\\"SharePoint\\\", \\\"SPO/OneDrive\\\")\\r\\n| where Operation == \\\"FileDownloaded\\\"\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by bin(Timestamp, detectionWindow), [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Bulk File Download\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"DownloadCount\",\"formatter\":8,\"formatOptions\":{\"palette\":\"blue\"}}]}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Threat\"},\"name\":\"query - 4\"}]},\"name\":\"group - 9\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\\r\\n| extend \\r\\n Country = tostring(GeoInfo.country), \\r\\n State = tostring(GeoInfo.state), \\r\\n City = tostring(GeoInfo.city), \\r\\n Latitude = tostring(GeoInfo.latitude), \\r\\n Longitude = tostring(GeoInfo.longitude)\\r\\n| project \\r\\n UserId, \\r\\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\\r\\n Latitude, \\r\\n Longitude, \\r\\n City, \\r\\n State, \\r\\n Country\\r\\n | where tostring(Country) != \\\"\\\"\\r\\n| summarize Count = count() by City, State, Country\\r\\n\\r\\n\\r\\n\",\"size\":0,\"title\":\"Access By Location\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"Country\",\"latitude\":\"Latitude\",\"longitude\":\"Longitude\",\"sizeSettings\":\"Count\",\"sizeAggregation\":\"Sum\",\"minSize\":20,\"labelSettings\":\"Country\",\"legendMetric\":\"Country\",\"numberOfMetrics\":8,\"legendAggregation\":\"Count\",\"itemColorSettings\":{\"nodeColorField\":\"Count\",\"colorAggregation\":\"Sum\",\"type\":\"heatmap\",\"heatmapPalette\":\"turquoise\"}}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 5\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| summarize [\\\"Event Count\\\"] = count() by [\\\"Time Generated\\\"] = TimeGenerated, [\\\"User\\\"] = UserId, [\\\"Source IP\\\"] = SourceIp, [\\\"Device Operating System\\\"] = DeviceOperatingSystem, [\\\"Workload\\\"] = Workload\\r\\n| order by [\\\"Time Generated\\\"] desc\\r\\n\",\"size\":0,\"title\":\"Access By Location And OS\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"rowLimit\":1000,\"filter\":true}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 1\"}]},\"name\":\"group - 19\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let data = EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| project \\r\\n Operation, \\r\\n UserId, \\r\\n Workload, \\r\\n SourceIp, \\r\\n DeviceId, \\r\\n TimeGenerated, \\r\\n Details = pack_all(),\\r\\n OS = tostring(DeviceOperatingSystem)\\r\\n| extend Workload = tostring(Workload)\\r\\n| extend WorkloadStatus = case(\\r\\n Workload == \\\"Exchange\\\", \\\"Exchange\\\",\\r\\n Workload == \\\"Teams\\\", \\\"Teams\\\",\\r\\n Workload == \\\"SharePoint\\\", \\\"SharePoint\\\",\\r\\n \\\"Other\\\"\\r\\n);\\r\\n\\r\\nlet appData = data\\r\\n| summarize \\r\\n TotalCount = count(), \\r\\n ExchangeCount = countif(Workload == \\\"Exchange\\\"), \\r\\n TeamsCount = countif(Workload == \\\"Teams\\\"), \\r\\n SharePointCount = countif(Workload == \\\"SharePoint\\\"), \\r\\n OtherCount = countif(Workload == \\\"Other\\\") \\r\\n by UserId\\r\\n| where UserId != ''\\r\\n| join kind=inner (\\r\\n data\\r\\n | make-series Trend = count() default = 0 on TimeGenerated in range({TimeRange:start}, {TimeRange:end}, {TimeRange:grain}) by UserId\\r\\n | project-away TimeGenerated\\r\\n ) on UserId\\r\\n| order by TotalCount desc, UserId asc\\r\\n| project UserId, TotalCount, ExchangeCount, TeamsCount, SharePointCount, OtherCount, Trend\\r\\n| serialize Id = row_number();\\r\\n\\r\\ndata\\r\\n| summarize \\r\\n TotalCount = count(), \\r\\n ExchangeCount = countif(Workload == \\\"Exchange\\\"), \\r\\n TeamsCount = countif(Workload == \\\"Teams\\\"), \\r\\n SharePointCount = countif(Workload == \\\"SharePoint\\\"), \\r\\n OtherCount = countif(Workload == \\\"Other\\\") \\r\\n by UserId, SourceIp = tostring(SourceIp)\\r\\n| join kind=inner (\\r\\n data\\r\\n | make-series Trend = count() default = 0 on TimeGenerated in range({TimeRange:start}, {TimeRange:end}, {TimeRange:grain}) by UserId, SourceIp\\r\\n | project-away TimeGenerated\\r\\n ) on UserId, SourceIp\\r\\n| order by TotalCount desc, UserId asc\\r\\n| project UserId, SourceIp, TotalCount, ExchangeCount, TeamsCount, SharePointCount, OtherCount, Trend\\r\\n| serialize Id = row_number(1000000)\\r\\n| join kind=inner (appData) on UserId\\r\\n| project Id, Name = SourceIp, Type = 'Client IP', ['Events Count'] = TotalCount, Trend, ['Exchange Events'] = ExchangeCount, ['Teams Events'] = TeamsCount, ['SharePoint Events'] = SharePointCount, ['Other Count'] = OtherCount, ParentId = Id1\\r\\n| union (\\r\\n appData \\r\\n | project Id, Name = UserId, Type = 'Operating System', ['Events Count'] = TotalCount, Trend, ['Exchange Events'] = ExchangeCount, ['Teams Events'] = TeamsCount, ['SharePoint Events'] = SharePointCount, ['Other Count'] = OtherCount, ParentId = -1\\r\\n )\\r\\n| order by ['Events Count'] desc, Name asc\\r\\n\",\"size\":2,\"title\":\"Activity Log\",\"timeContextFromParameter\":\"TimeRange\",\"showRefreshButton\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Id\",\"formatter\":5},{\"columnMatch\":\"Type\",\"formatter\":5},{\"columnMatch\":\"Trend\",\"formatter\":9,\"formatOptions\":{\"palette\":\"blue\"}},{\"columnMatch\":\"Other Count\",\"formatter\":5},{\"columnMatch\":\"ParentId\",\"formatter\":5},{\"columnMatch\":\"Operation Count\",\"formatter\":8,\"formatOptions\":{\"palette\":\"blue\"}},{\"columnMatch\":\"Exchange Count\",\"formatter\":8,\"formatOptions\":{\"min\":0,\"palette\":\"blue\"}},{\"columnMatch\":\"Teams Count\",\"formatter\":8,\"formatOptions\":{\"min\":0,\"palette\":\"purple\"}},{\"columnMatch\":\"SharePoint Count\",\"formatter\":8,\"formatOptions\":{\"min\":0,\"palette\":\"turquoise\"}},{\"columnMatch\":\"Details\",\"formatter\":5,\"formatOptions\":{\"linkTarget\":\"GenericDetails\",\"linkIsContextBlade\":true}}],\"rowLimit\":1000,\"filter\":true,\"hierarchySettings\":{\"idColumn\":\"Id\",\"parentColumn\":\"ParentId\",\"treeType\":0,\"expanderColumn\":\"Name\"}}},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 6\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
\\r\\nšŸ’” _Click on a segment of the pie chart to explore more details_\"},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"text - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| extend \\r\\n OS = coalesce(DeviceOperatingSystem, \\\"Unknown OS\\\"),\\r\\n OSVersion = coalesce(tostring(DeviceOperatingSystemVersion), \\\"Unknown Version\\\")\\r\\n| summarize DeviceCount = count() by OS, OSVersion\\r\\n| order by DeviceCount desc\\r\\n\",\"size\":3,\"title\":\"Devices Accessing M365\",\"timeContextFromParameter\":\"TimeRange\",\"exportParameterName\":\"Parampie\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"piechart\"},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| extend \\r\\n [\\\"Operating System\\\"] = coalesce(DeviceOperatingSystem, \\\"Unknown OS\\\"),\\r\\n [\\\"OS Version\\\"] = coalesce(tostring(DeviceOperatingSystemVersion), \\\"Unknown Version\\\"),\\r\\n [\\\"Device ID\\\"] = coalesce(tostring(DeviceId), \\\"Unknown DeviceId\\\")\\r\\n| where [\\\"Operating System\\\"] == dynamic({Parampie}).label\\r\\n| summarize [\\\"Device Count\\\"] = count() by [\\\"Operating System\\\"], [\\\"OS Version\\\"], [\\\"Device ID\\\"]\\r\\n| order by [\\\"Device Count\\\"] desc\\r\\n\",\"size\":2,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"$gen_group\",\"formatter\":1}],\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"OperatingSystem\",\"OSVersion\"],\"expandTopLevel\":false}}},\"customWidth\":\"50\",\"conditionalVisibilities\":[{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},{\"parameterName\":\"Parampie\",\"comparison\":\"isNotEqualTo\"}],\"name\":\"query - 1\"}]},\"name\":\"group - 19\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let BusinessHoursStart = 8; // 8 AM\\r\\nlet BusinessHoursEnd = 18; // 6 PM\\r\\n\\r\\nEnrichedMicrosoft365AuditLogs \\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| extend HourOfDay = hourofday(TimeGenerated)\\r\\n| where HourOfDay < BusinessHoursStart or HourOfDay > BusinessHoursEnd\\r\\n| summarize [\\\"Off-Hour Activities\\\"] = count() by [\\\"User ID\\\"] = UserId, [\\\"Date\\\"] = bin(TimeGenerated, 1d), [\\\"Operation\\\"]\\r\\n| order by [\\\"Off-Hour Activities\\\"] desc\\r\\n\",\"size\":0,\"title\":\"Activity Outside Standard Working Hours (8:00 - 18:00)\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 14\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\n| summarize Count = count() by bin(TimeGenerated, 1h)\\n| order by TimeGenerated asc\\n\",\"size\":0,\"title\":\"Microsoft 365 Transactions\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"unstackedbar\"},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 2\"}],\"fallbackResourceIds\":[\"Global Secure Access\"],\"fromTemplateId\":\"GSA Enriched Microsoft 365 logs\",\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", "version": "1.0", "sourceId": "[variables('workspaceResourceId')]", "category": "sentinel" @@ -344,7 +344,7 @@ "apiVersion": "2022-01-01-preview", "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('Workbook-', last(split(variables('workbookId1'),'/'))))]", "properties": { - "description": "@{workbookKey=GSAM365EnrichedEvents; logoFileName=gsa.svg; description=This Workbook provides a detailed view of Microsoft 365 log data, enriched with contextual information to enhance visibility into user activities and potential security threats.; dataTypesDependencies=System.Object[]; dataConnectorsDependencies=System.Object[]; previewImagesFileNames=System.Object[]; version=1.0.0; title=Microsoft Global Secure Access Enriched M365 Logs; templateRelativePath=GSAM365EnrichedEvents.json; provider=Microsoft}.description", + "description": "@{workbookKey=GSAM365EnrichedEvents; logoFileName=gsa.svg; description=This Workbook provides a detailed view of Microsoft 365 log data, enriched with contextual information to enhance visibility into user activities and potential security threats.; dataTypesDependencies=System.Object[]; dataConnectorsDependencies=System.Object[]; previewImagesFileNames=System.Object[]; version=1.0.1; title=Microsoft Global Secure Access Enriched M365 Logs; templateRelativePath=GSAM365EnrichedEvents.json; provider=Microsoft}.description", "parentId": "[variables('workbookId1')]", "contentId": "[variables('_workbookContentId1')]", "kind": "Workbook", @@ -417,7 +417,7 @@ }, "properties": { "displayName": "[parameters('workbook2-name')]", - "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"## Traffic Logs workbook\\n---\\n\\nLog information in the dashboard is limited to 30 days.\"},\"name\":\"text - 0\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"\"],\"parameters\":[{\"id\":\"ff8b2a55-1849-4848-acf8-eab5452e9f10\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"LogAnalyticWorkspace\",\"label\":\"Log Analytic Workspace\",\"type\":5,\"description\":\"The log analytic workspace in which to execute the queries\",\"isRequired\":true,\"query\":\"resources\\r\\n| where type == \\\"microsoft.operationalinsights/workspaces\\\"\\r\\n| project id\",\"typeSettings\":{\"resourceTypeFilter\":{\"microsoft.operationalinsights/workspaces\":true},\"additionalResourceOptions\":[\"value::1\"],\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"defaultValue\":\"value::1\",\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},{\"id\":\"f15f34d8-8e2d-4c39-8dee-be2f979c86a8\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TimeRange\",\"label\":\"Time Range\",\"type\":4,\"isRequired\":true,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":300000},{\"durationMs\":900000},{\"durationMs\":1800000},{\"durationMs\":3600000},{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000}],\"allowCustom\":true},\"timeContext\":{\"durationMs\":86400000},\"value\":{\"durationMs\":2592000000}},{\"id\":\"8bab511b-53b3-4220-9d1c-372345b06728\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Users\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"EnrichedMicrosoft365AuditLogsDemos_CL\\r\\n| summarize Count = count() by UserId_s\\r\\n| order by Count desc, UserId_s asc\\r\\n| project Value = UserId_s, Label = strcat(UserId_s, ' - ', Count, ' Logs'), Selected = false\",\"typeSettings\":{\"limitSelectTo\":20,\"additionalResourceOptions\":[\"value::all\"],\"selectAllValue\":\"*\",\"showDefault\":false},\"timeContext\":{\"durationMs\":0},\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":[\"value::all\"]}],\"style\":\"pills\",\"queryType\":1,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 15\"},{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"tabs\",\"links\":[{\"id\":\"2b2cd1be-9d25-412c-8444-f005c4789b55\",\"cellValue\":\"tabSel\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Overview\",\"subTarget\":\"Overview\",\"style\":\"link\"},{\"id\":\"cc3e67f2-f20f-4430-8dee-d0773b90d9ce\",\"cellValue\":\"tabSel\",\"linkTarget\":\"parameter\",\"linkLabel\":\"All Traffic\",\"subTarget\":\"AllTraffic\",\"style\":\"link\"}]},\"name\":\"links - 7\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessDemo_CL\\r\\n| project \\r\\n Timestamp = createdDateTime_t,\\r\\n User = userPrincipalName_s,\\r\\n SourceIP = SourceIP,\\r\\n DestinationIP = destinationIp_s,\\r\\n DestinationPort = destinationPort_d,\\r\\n Action = action_s,\\r\\n PolicyName = policyName_s,\\r\\n TransportProtocol = transportProtocol_s,\\r\\n TrafficType = trafficType_s,\\r\\n DestinationURL = destinationUrl_s,\\r\\n ReceivedBytes = receivedBytes_d,\\r\\n SentBytes = sentBytes_d,\\r\\n DeviceOS = deviceOperatingSystem_s,\\r\\n PolicyRuleID = policyRuleId_s\\r\\n| order by Timestamp desc\",\"size\":3,\"showAnalytics\":true,\"title\":\"Log\",\"timeContextFromParameter\":\"TimeRange\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"rowLimit\":1000,\"filter\":true}},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"AllTraffic\"},\"name\":\"query - 6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"// Unique Users\\nNetworkAccessDemo_CL\\n| extend GeoInfo = geo_info_from_ip_address(SourceIP) // Extend each row with geolocation info\\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\\n| project SourceIP, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\\n| summarize UniqueUsers=dcount(Country)\\n| extend snapshot = \\\"Total Locations\\\"\\n| project col1 = UniqueUsers, snapshot\\n\\n// Union with Unique Devices\\n| union (\\n NetworkAccessDemo_CL\\n | where userPrincipalName_s in ({Users}) or '*' in ({Users})\\n | extend BytesInGB = todouble(sentBytes_d + receivedBytes_d) / (1024 * 1024 * 1024) // Convert bytes to gigabytes\\n | summarize TotalBytesGB = sum(BytesInGB)\\n | extend snapshot = \\\"Total Bytes (GB)\\\"\\n | project col1 = tolong(TotalBytesGB), snapshot\\n)\\n\\n// Union with Total Internet Access\\n| union (\\n NetworkAccessDemo_CL\\n | where userPrincipalName_s in ({Users}) or '*' in ({Users})\\n | summarize TotalTransactions = count()\\n | extend snapshot = \\\"Total Trasnacations\\\"\\n | project col1 = TotalTransactions, snapshot\\n)\\n\\n// Union with Total Private Access\\n// Order by Snapshot for consistent tile ordering on dashboard\\n| order by snapshot\",\"size\":4,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"snapshot\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"col1\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"}},\"showBorder\":true,\"size\":\"auto\"},\"mapSettings\":{\"locInfo\":\"LatLong\",\"sizeSettings\":\"ExistingClients\",\"sizeAggregation\":\"Sum\",\"legendMetric\":\"ExistingClients\",\"legendAggregation\":\"Sum\",\"itemColorSettings\":{\"type\":\"heatmap\",\"colorAggregation\":\"Sum\",\"nodeColorField\":\"ExistingClients\",\"heatmapPalette\":\"greenRed\"}},\"textSettings\":{\"style\":\"bignumber\"}},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 2\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessDemo_CL\\r\\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\\r\\n| extend BytesInGB = todouble(sentBytes_d + receivedBytes_d) / (1024 * 1024 * 1024) // Convert bytes to gigabytes\\r\\n| summarize TotalBytesGB = sum(BytesInGB) by bin(createdDateTime_t, 1h), trafficType_s\\r\\n| order by bin(createdDateTime_t, 1h) asc, trafficType_s asc\\r\\n| project createdDateTime_t, trafficType_s, TotalBytesGB\\r\\n\",\"size\":2,\"title\":\"Usage over Time (GB)\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"barchart\"},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 0\"}]},\"name\":\"group - 5\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessDemo_CL\\r\\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\\r\\n| extend GeoInfo = geo_info_from_ip_address(SourceIP) // Extend each row with geolocation info\\r\\n| project createdDateTime_t, SourceIP, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\\r\\n| where Country != \\\"\\\"\\r\\n| summarize Count = count() by City, State, Country\\r\\n\\r\\n\",\"size\":0,\"title\":\"Locations\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"Country\",\"latitude\":\"Latitude\",\"longitude\":\"Longitude\",\"sizeSettings\":\"Count\",\"sizeAggregation\":\"Sum\",\"labelSettings\":\"Country\",\"legendMetric\":\"Country\",\"legendAggregation\":\"Count\",\"itemColorSettings\":{\"nodeColorField\":\"Count\",\"colorAggregation\":\"Sum\",\"type\":\"heatmap\",\"heatmapPalette\":\"turquoise\"}}},\"customWidth\":\"50\",\"name\":\"query - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessDemo_CL\\r\\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\\r\\n| where tolower(action_s) == \\\"allow\\\" and destinationWebCategory_displayName_s != '' // Filter for allowed traffic\\r\\n| extend firstCategory = tostring(split(destinationWebCategory_displayName_s, ',')[0]) // Split and get the first category\\r\\n| summarize Count = count() by firstCategory\\r\\n| top 10 by Count\\r\\n\",\"size\":2,\"title\":\"Top Allowed Web Categories\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\"},\"customWidth\":\"50\",\"name\":\"query - 7\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessDemo_CL\\r\\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\\r\\n| where tolower(action_s) == \\\"block\\\" and destinationFQDN_s != '' // Filter for allowed traffic\\r\\n| summarize Count = count() by destinationFQDN_s\\r\\n| top 100 by Count\",\"size\":0,\"title\":\"Top Blocked Destinations\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"customWidth\":\"50\",\"name\":\"query - 5\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessDemo_CL\\r\\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\\r\\n| where tolower(action_s) == \\\"block\\\" and destinationWebCategory_displayName_s != '' // Filter for blocked traffic\\r\\n| extend firstCategory = tostring(split(destinationWebCategory_displayName_s, ',')[0]) // Split and get the first category\\r\\n| summarize Count = count() by firstCategory\\r\\n| top 10 by Count\\r\\n\",\"size\":3,\"title\":\"Top Blocked Web Categories\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\"},\"customWidth\":\"50\",\"name\":\"query - 6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessDemo_CL\\r\\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\\r\\n| where sentBytes_d > 0\\r\\n| where tolower(action_s) != \\\"block\\\" \\r\\n| summarize Count = count() , Sent = sum(sentBytes_d), Recived = sum(receivedBytes_d), Total = sum(receivedBytes_d+ sentBytes_d) by destinationFQDN_s\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Top Allowed Destinations\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Count\",\"formatter\":4,\"formatOptions\":{\"palette\":\"magenta\"}},{\"columnMatch\":\"Recived\",\"formatter\":4,\"formatOptions\":{\"palette\":\"turquoise\"}},{\"columnMatch\":\"Total\",\"formatter\":4,\"formatOptions\":{\"palette\":\"pink\"}},{\"columnMatch\":\"Sent\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\"}}]}},\"customWidth\":\"50\",\"name\":\"query - 1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessDemo_CL\\r\\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\\r\\n| where transportProtocol_s != ''\\r\\n| summarize Count = count() by toupper(transportProtocol_s)\\r\\n| top 10 by Count\\r\\n\",\"size\":2,\"title\":\"Protocol Distburion\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"piechart\"},\"customWidth\":\"50\",\"name\":\"query - 3\"}]},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"group - 4\"}],\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", + "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"## Network Traffic Insights Workbook (Preview)\\n---\\nInformation in the dashboard is based on log data\\n\\n\\n\"},\"name\":\"text - 2\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"value::all\"],\"parameters\":[{\"id\":\"ff8b2a55-1849-4848-acf8-eab5452e9f10\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"LogAnalyticWorkspace\",\"label\":\"Log Analytic Workspace\",\"type\":5,\"description\":\"The Log Analytic Workspace In Which To Execute The Queries\",\"isRequired\":true,\"query\":\"resources\\r\\n| where type == \\\"microsoft.operationalinsights/workspaces\\\"\\r\\n| project id\",\"crossComponentResources\":[\"value::all\"],\"typeSettings\":{\"resourceTypeFilter\":{\"microsoft.operationalinsights/workspaces\":true},\"showDefault\":false},\"timeContext\":{\"durationMs\":2592000000},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},{\"id\":\"f15f34d8-8e2d-4c39-8dee-be2f979c86a8\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TimeRange\",\"label\":\"Time Range\",\"type\":4,\"isRequired\":true,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":300000},{\"durationMs\":900000},{\"durationMs\":1800000},{\"durationMs\":3600000},{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000}],\"allowCustom\":true},\"timeContext\":{\"durationMs\":86400000},\"value\":{\"durationMs\":604800000}},{\"id\":\"8bab511b-53b3-4220-9d1c-372345b06728\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Users\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"NetworkAccessTraffic\\r\\n| summarize Count = count() by UserPrincipalName\\r\\n| order by Count desc, UserPrincipalName asc\\r\\n| project Value = UserPrincipalName, Label = strcat(UserPrincipalName, ' - ', Count, ' Logs'), Selected = false\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"typeSettings\":{\"limitSelectTo\":20,\"additionalResourceOptions\":[\"value::all\"],\"selectAllValue\":\"*\",\"showDefault\":false},\"timeContext\":{\"durationMs\":0},\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":[\"value::all\"]},{\"id\":\"527af4d2-3089-4aa4-9fbb-48ec697db20d\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"WebCategories\",\"label\":\"Web Categories\",\"type\":2,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"NetworkAccessTraffic\\r\\n| extend firstCategory = tostring(split(DestinationWebCategories, ',')[0]) // Split and get the first category\\r\\n| summarize Count = count() by firstCategory\\r\\n| order by Count desc, firstCategory asc\\r\\n| project Value = firstCategory, Label = strcat(firstCategory, ' - ', Count, ' Logs')\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"typeSettings\":{\"additionalResourceOptions\":[\"value::all\"],\"selectAllValue\":\"*\",\"showDefault\":false},\"timeContext\":{\"durationMs\":604800000},\"timeContextFromParameter\":\"TimeRange\",\"defaultValue\":\"value::all\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":[\"value::all\"]}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 15\"},{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"tabs\",\"links\":[{\"id\":\"2b2cd1be-9d25-412c-8444-f005c4789b55\",\"cellValue\":\"tabSel\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Overview\",\"subTarget\":\"Overview\",\"style\":\"link\"},{\"id\":\"cc3e67f2-f20f-4430-8dee-d0773b90d9ce\",\"cellValue\":\"tabSel\",\"linkTarget\":\"parameter\",\"linkLabel\":\"All Traffic\",\"subTarget\":\"AllTraffic\",\"style\":\"link\"},{\"id\":\"5ae54b5a-ac7b-4b7a-a1e1-1e574625caa3\",\"cellValue\":\"tabSel\",\"linkTarget\":\"parameter\",\"linkLabel\":\"URL Lookup\",\"subTarget\":\"URLLookup\",\"style\":\"link\"},{\"id\":\"68c566f8-957e-4a3f-8b66-730fc24135fb\",\"cellValue\":\"tabSel\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Security Insights\",\"subTarget\":\"Security\",\"style\":\"link\"}]},\"name\":\"links - 7\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let NetworkAccessTrafficData = NetworkAccessTraffic\\r\\n| where SourceIp != \\\"\\\" and isnotempty(SourceIp)\\r\\n| summarize arg_max(TimeGenerated, *) by SourceIp, TenantId;\\r\\n\\r\\nlet SigninLogsData = SigninLogs\\r\\n| where IPAddress != \\\"\\\" and isnotempty(IPAddress)\\r\\n| summarize arg_max(TimeGenerated, *) by IPAddress, TenantId, UserId, CorrelationId;\\r\\n\\r\\nSigninLogsData\\r\\n| join kind=leftanti (\\r\\n NetworkAccessTrafficData\\r\\n | where SourceIp != \\\"\\\" and isnotempty(SourceIp)\\r\\n) on $left.IPAddress == $right.SourceIp and $left.TenantId == $right.TenantId\\r\\n| project TimeGenerated, IPAddress, UserId, UserPrincipalName, AppDisplayName, DeviceDetail.deviceId\\r\\n\",\"size\":0,\"title\":\"Sign-ins Outside Global Secure Access\",\"timeContextFromParameter\":\"TimeRange\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"rowLimit\":1000,\"filter\":true}},\"name\":\"query - 0\"}]},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"Security\"},\"name\":\"group - 10\"},{\"type\":1,\"content\":{\"json\":\"šŸ’” Type the URL for detailed analysis\"},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"URLLookup\"},\"name\":\"text - 10\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"ec8c38c8-3064-4921-8dcd-a69d3895599b\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"URL\",\"type\":1,\"typeSettings\":{\"isSearchBox\":true},\"timeContext\":{\"durationMs\":86400000}}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"URLLookup\"},\"name\":\"parameters - 9\"},{\"type\":1,\"content\":{\"json\":\"# Web Categories\"},\"name\":\"text - 11\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let CategoryData = NetworkAccessTraffic \\r\\n| where DestinationFqdn contains \\\"{URL}\\\" // Filters logs based on the provided URL parameter\\r\\n| extend CategoriesList = split(DestinationWebCategories, \\\",\\\") // Split categories into a list\\r\\n| mv-expand Category = CategoriesList // Expand the list into individual rows\\r\\n| extend Category = trim_start(\\\" \\\", trim_end(\\\" \\\", tostring(Category))) // Trim leading and trailing spaces\\r\\n| extend Category = iff(TrafficType == \\\"microsoft365\\\", \\\"Microsoft M365\\\", Category) // Set category to \\\"Microsoft M365\\\" if TrafficType is \\\"microsoft365\\\"\\r\\n| summarize UniqueCategories = make_set(Category); // Create a set of unique categories\\r\\n\\r\\nCategoryData\\r\\n| extend \\r\\n PrimaryCategory = iff(array_length(UniqueCategories) > 0, tostring(UniqueCategories[0]), \\\"None\\\")\\r\\n| project \\r\\n col1 = PrimaryCategory,\\r\\n snapshot = \\\"Primary Category\\\"\\r\\n\\r\\n| union (\\r\\n CategoryData\\r\\n | extend \\r\\n SecondaryCategory = iff(array_length(UniqueCategories) > 1, tostring(UniqueCategories[1]), \\\"None\\\")\\r\\n | project \\r\\n col1 = SecondaryCategory,\\r\\n snapshot = \\\"Secondary Category\\\"\\r\\n)\\r\\n\\r\\n| order by snapshot asc\\r\\n\",\"size\":4,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"snapshot\"},\"leftContent\":{\"columnMatch\":\"col1\",\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"maximumFractionDigits\":2}}},\"showBorder\":true,\"size\":\"auto\"}},\"name\":\"query - 17\"},{\"type\":1,\"content\":{\"json\":\"# Traffic Access Details\"},\"name\":\"text - 15\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let NetworkData = NetworkAccessTraffic\\r\\n| where DestinationFqdn contains \\\"{URL}\\\"; // Filters logs based on the provided URL parameter\\r\\n\\r\\nNetworkData\\r\\n| summarize \\r\\n UniqueUsers = dcount(UserPrincipalName),\\r\\n UniqueDevices = dcount(DeviceId), \\r\\n TotalAllow = countif(Action == \\\"Allow\\\"),\\r\\n TotalBlock = countif(Action == \\\"Block\\\")\\r\\n| project \\r\\n col1 = UniqueUsers, \\r\\n snapshot = \\\"Unique Users\\\"\\r\\n| union (NetworkData\\r\\n | summarize UniqueDevices = dcount(DeviceId)\\r\\n | project col1 = UniqueDevices, snapshot = \\\"Unique Devices\\\")\\r\\n| union (NetworkData\\r\\n | summarize TotalAllow = countif(Action == \\\"Allow\\\")\\r\\n | project col1 = TotalAllow, snapshot = \\\"Total Allow\\\")\\r\\n| union (NetworkData\\r\\n | summarize TotalBlock = countif(Action == \\\"Block\\\")\\r\\n | project col1 = TotalBlock, snapshot = \\\"Total Block\\\")\\r\\n| order by snapshot asc\",\"size\":4,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"snapshot\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"col1\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"maximumFractionDigits\":2,\"maximumSignificantDigits\":3}}},\"showBorder\":true}},\"name\":\"query - 14\"},{\"type\":1,\"content\":{\"json\":\"# Bandwidth Usage\"},\"name\":\"text - 16\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let NetworkData = NetworkAccessTraffic\\r\\n| where DestinationFqdn contains \\\"{URL}\\\"; // Filters logs based on the provided URL parameter\\r\\n\\r\\nNetworkData\\r\\n| summarize \\r\\n TotalSent = sum(SentBytes),\\r\\n TotalReceived = sum(ReceivedBytes),\\r\\n TotalBandwidth = sum(SentBytes + ReceivedBytes)\\r\\n| extend \\r\\n TotalSentMB = round(TotalSent / 1024.0 / 1024.0, 2),\\r\\n TotalReceivedMB = round(TotalReceived / 1024.0 / 1024.0, 2),\\r\\n TotalBandwidthMB = round(TotalBandwidth / 1024.0 / 1024.0, 2)\\r\\n| project \\r\\n TotalSentMB,\\r\\n TotalReceivedMB,\\r\\n TotalBandwidthMB\\r\\n| extend dummy = 1\\r\\n| project-away dummy\\r\\n| mv-expand \\r\\n Column = pack_array(\\\"TotalSentMB\\\", \\\"TotalReceivedMB\\\", \\\"TotalBandwidthMB\\\"),\\r\\n Value = pack_array(TotalSentMB, TotalReceivedMB, TotalBandwidthMB)\\r\\n| extend \\r\\n snapshot = case(\\r\\n Column == \\\"TotalSentMB\\\", \\\"Total Sent (MB)\\\",\\r\\n Column == \\\"TotalReceivedMB\\\", \\\"Total Received (MB)\\\",\\r\\n Column == \\\"TotalBandwidthMB\\\", \\\"Total Bandwidth (MB)\\\",\\r\\n \\\"Unknown\\\"\\r\\n ),\\r\\n col1 = iff(Value < 0.01, 0.00, Value)\\r\\n| project-away Column, Value\\r\\n| order by snapshot asc\",\"size\":4,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"snapshot\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"col1\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"maximumFractionDigits\":2,\"maximumSignificantDigits\":3}}},\"showBorder\":true}},\"name\":\"query - 17\"},{\"type\":1,\"content\":{\"json\":\"# Traffic Logs (filtered)\"},\"name\":\"text - 12\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| where DestinationFqdn contains \\\"{URL}\\\"\\r\\n| project \\r\\n Timestamp = TimeGenerated,\\r\\n User = UserPrincipalName,\\r\\n [\\\"Source IP\\\"] = SourceIp,\\r\\n [\\\"Destination IP\\\"] = DestinationIp,\\r\\n [\\\"Destination Port\\\"] = DestinationPort,\\r\\n [\\\"Destination FQDN\\\"] = DestinationFqdn,\\r\\n Action = case(\\r\\n tolower(Action) == \\\"allow\\\", \\\"šŸŸ¢ Allow\\\", \\r\\n tolower(Action) == \\\"block\\\", \\\"šŸ”“ Block\\\", \\r\\n tolower(Action) // This returns the action in lowercase if it doesn't match \\\"allow\\\" or \\\"block\\\"\\r\\n ),\\r\\n [\\\"Policy Name\\\"] = PolicyName,\\r\\n [\\\"Policy Rule ID\\\"] = PolicyRuleId,\\r\\n [\\\"Received Bytes\\\"] = ReceivedBytes,\\r\\n [\\\"Sent Bytes\\\"] = SentBytes,\\r\\n [\\\"Device OS\\\"] = DeviceOperatingSystem \\r\\n| order by Timestamp desc\\r\\n\",\"size\":0,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"name\":\"query - 13\"}]},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"URLLookup\"},\"name\":\"group - URL Lookup\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| project \\r\\n Timestamp = TimeGenerated,\\r\\n User = UserPrincipalName,\\r\\n [\\\"Source IP\\\"] = SourceIp,\\r\\n [\\\"Destination IP\\\"] = DestinationIp,\\r\\n [\\\"Destination Port\\\"] = DestinationPort,\\r\\n [\\\"Destination FQDN\\\"] = DestinationFqdn,\\r\\n Action = case(\\r\\n tolower(Action) == \\\"allow\\\", \\\"šŸŸ¢ Allow\\\", \\r\\n tolower(Action) == \\\"block\\\", \\\"šŸ”“ Block\\\", \\r\\n tolower(Action) // This returns the action in lowercase if it doesn't match \\\"allow\\\" or \\\"block\\\"\\r\\n ),\\r\\n [\\\"Policy Name\\\"] = PolicyName,\\r\\n [\\\"Web Category\\\"] = DestinationWebCategories,\\r\\n [\\\"Transport Protocol\\\"] = TransportProtocol,\\r\\n [\\\"Traffic Type\\\"] = TrafficType,\\r\\n [\\\"Received Bytes\\\"] = ReceivedBytes,\\r\\n [\\\"Sent Bytes\\\"] = SentBytes,\\r\\n [\\\"Device OS\\\"] = DeviceOperatingSystem,\\r\\n [\\\"Policy Rule ID\\\"] = PolicyRuleId\\r\\n| order by Timestamp desc\\r\\n\",\"size\":3,\"title\":\"Traffic Logs\",\"timeContextFromParameter\":\"TimeRange\",\"showRefreshButton\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"rowLimit\":1000,\"filter\":true}},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"AllTraffic\"},\"name\":\"query - 6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"// Unique Users\\nNetworkAccessTraffic\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Extend each row with geolocation info\\n| project SourceIp, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\\n| summarize UniqueUsers=dcount(Country)\\n| extend snapshot = \\\"Total Locations\\\"\\n| project col1 = UniqueUsers, snapshot\\n\\n// Union with Unique Devices\\n| union (\\n NetworkAccessTraffic\\n | where UserPrincipalName in ({Users}) or '*' in ({Users})\\n | extend BytesInGB = todouble(SentBytes + ReceivedBytes) / (1024 * 1024 * 1024) // Convert bytes to gigabytes\\n | summarize TotalBytesGB = sum(BytesInGB)\\n | extend snapshot = \\\"Total Bytes (GB)\\\"\\n | project col1 = tolong(TotalBytesGB), snapshot\\n)\\n\\n// Union with Total Internet Access\\n| union (\\n NetworkAccessTraffic\\n | where UserPrincipalName in ({Users}) or '*' in ({Users})\\n | summarize TotalTransactions = count()\\n | extend snapshot = \\\"Total Transactions\\\"\\n | project col1 = TotalTransactions, snapshot\\n)\\n\\n// Union with Total Private Access\\n// Order by Snapshot for consistent tile ordering on dashboard\\n| order by snapshot\",\"size\":4,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"snapshot\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"col1\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"}},\"showBorder\":true,\"size\":\"auto\"},\"mapSettings\":{\"locInfo\":\"LatLong\",\"sizeSettings\":\"ExistingClients\",\"sizeAggregation\":\"Sum\",\"legendMetric\":\"ExistingClients\",\"legendAggregation\":\"Sum\",\"itemColorSettings\":{\"type\":\"heatmap\",\"colorAggregation\":\"Sum\",\"nodeColorField\":\"ExistingClients\",\"heatmapPalette\":\"greenRed\"}},\"textSettings\":{\"style\":\"bignumber\"}},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 2\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| extend BytesIn = todouble(SentBytes + ReceivedBytes) / (1024 * 1024) // Convert bytes to Mbytes\\r\\n| summarize TotalBytesMB = sum(BytesIn) by bin(TimeGenerated, 1h), TrafficType\\r\\n| order by bin(TimeGenerated, 1h) asc, TrafficType asc\\r\\n| project TimeGenerated, TrafficType, TotalBytesMB\\r\\n\",\"size\":2,\"title\":\"Usage Over Time (MB)\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"barchart\"},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 0\"}]},\"name\":\"group - 5\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Extend each row with geolocation info\\r\\n| project TimeGenerated, SourceIp, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\\r\\n| where Country != \\\"\\\"\\r\\n| summarize Count = count() by City, State, Country\\r\\n\\r\\n\",\"size\":3,\"title\":\"Locations\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"Country\",\"latitude\":\"Latitude\",\"longitude\":\"Longitude\",\"sizeSettings\":\"Count\",\"sizeAggregation\":\"Sum\",\"labelSettings\":\"Country\",\"legendMetric\":\"Country\",\"legendAggregation\":\"Count\",\"itemColorSettings\":{\"nodeColorField\":\"Count\",\"colorAggregation\":\"Sum\",\"type\":\"heatmap\",\"heatmapPalette\":\"turquoise\"}}},\"name\":\"query - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| where tolower(Action) == \\\"allow\\\" and DestinationWebCategories != '' // Filter for allowed traffic\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| extend firstCategory = tostring(split(DestinationWebCategories, ',')[0]) // Split and get the first category\\r\\n| summarize Count = count() by firstCategory\\r\\n| top 10 by Count\\r\\n\",\"size\":2,\"title\":\"Top Allowed Web Categories\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"piechart\"},\"customWidth\":\"50\",\"name\":\"query - 7\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| where tolower(Action) == \\\"block\\\" and DestinationWebCategories != '' // Filter for blocked traffic\\r\\n| extend firstCategory = tostring(split(DestinationWebCategories, ',')[0]) // Split and get the first category\\r\\n| summarize Count = count() by firstCategory\\r\\n| top 10 by Count\\r\\n\",\"size\":3,\"title\":\"Top Blocked Web Categories\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"piechart\"},\"customWidth\":\"50\",\"name\":\"query - 6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic \\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| where tolower(Action) == \\\"block\\\" and DestinationFqdn != '' // Filter for blocked traffic with non-empty Destination FQDN\\r\\n| summarize Count = count() by [\\\"Destination FQDN\\\"] = DestinationFqdn, [\\\"Destination Web Categories\\\"] = DestinationWebCategories, [\\\"Policy Name\\\"] = PolicyName\\r\\n| order by Count\\r\\n\",\"size\":0,\"title\":\"Top Blocked Destinations\",\"timeContextFromParameter\":\"TimeRange\",\"showRefreshButton\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Count\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\"}}],\"rowLimit\":1000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"$gen_bar_Count_3\",\"sortOrder\":2}]},\"sortBy\":[{\"itemKey\":\"$gen_bar_Count_3\",\"sortOrder\":2}]},\"customWidth\":\"50\",\"name\":\"query - 5\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| where SentBytes > 0\\r\\n| where tolower(Action) != \\\"block\\\"\\r\\n| summarize \\r\\n Count = count(), \\r\\n [\\\"Sent Bytes\\\"] = sum(SentBytes), \\r\\n [\\\"Received Bytes\\\"] = sum(ReceivedBytes), \\r\\n [\\\"Total Bytes\\\"] = sum(ReceivedBytes + SentBytes) \\r\\n by [\\\"Destination FQDN\\\"] = DestinationFqdn\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Top Allowed Destinations\",\"timeContextFromParameter\":\"TimeRange\",\"showRefreshButton\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Count\",\"formatter\":4,\"formatOptions\":{\"palette\":\"magenta\"}},{\"columnMatch\":\"Recived\",\"formatter\":4,\"formatOptions\":{\"palette\":\"turquoise\"}},{\"columnMatch\":\"Total\",\"formatter\":4,\"formatOptions\":{\"palette\":\"pink\"}},{\"columnMatch\":\"Sent\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\"}}],\"rowLimit\":1000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"$gen_bar_Count_1\",\"sortOrder\":2}]},\"sortBy\":[{\"itemKey\":\"$gen_bar_Count_1\",\"sortOrder\":2}]},\"customWidth\":\"50\",\"name\":\"query - 1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where TransportProtocol != ''\\r\\n| summarize Count = count() by toupper(TransportProtocol)\\r\\n| top 10 by Count\\r\\n\",\"size\":2,\"title\":\"Protocol Distribution\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"piechart\"},\"customWidth\":\"50\",\"name\":\"query - 3\"}]},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"group - 4\"}],\"fallbackResourceIds\":[\"Global Secure Access\"],\"fromTemplateId\":\"GSA Network Traffic Insights\",\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", "version": "1.0", "sourceId": "[variables('workspaceResourceId')]", "category": "sentinel" @@ -428,7 +428,7 @@ "apiVersion": "2022-01-01-preview", "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('Workbook-', last(split(variables('workbookId2'),'/'))))]", "properties": { - "description": "@{workbookKey=GSANetworkTraffic; logoFileName=gsa.svg; description=This workbook provides an overview of all traffic logs within your network, offering insights into data transfer, anomalies, and potential threats.; dataTypesDependencies=System.Object[]; dataConnectorsDependencies=System.Object[]; previewImagesFileNames=System.Object[]; version=1.0.0; title=Microsoft Global Secure Access Traffic Logs; templateRelativePath=GSANetworkTraffic.json; subtitle=; provider=Microsoft}.description", + "description": "@{workbookKey=GSANetworkTraffic; logoFileName=gsa.svg; description=This workbook provides an overview of all traffic logs within your network, offering insights into data transfer, anomalies, and potential threats.; dataTypesDependencies=System.Object[]; dataConnectorsDependencies=System.Object[]; previewImagesFileNames=System.Object[]; version=1.0.1; title=Microsoft Global Secure Access Traffic Logs; templateRelativePath=GSANetworkTraffic.json; subtitle=; provider=Microsoft}.description", "parentId": "[variables('workbookId2')]", "contentId": "[variables('_workbookContentId2')]", "kind": "Workbook", diff --git a/Solutions/Global Secure Access/Workbooks/GSAM365EnrichedEvents.json b/Solutions/Global Secure Access/Workbooks/GSAM365EnrichedEvents.json index 39b4440aa1..ea40be7808 100644 --- a/Solutions/Global Secure Access/Workbooks/GSAM365EnrichedEvents.json +++ b/Solutions/Global Secure Access/Workbooks/GSAM365EnrichedEvents.json @@ -977,6 +977,6 @@ "fallbackResourceIds": [ "Global Secure Access" ], - "fromTemplateId": "community-Workbooks/Global Secure Access/Microsoft Entra Internet Access/Enriched Microsoft 365 logs", + "fromTemplateId": "GSA Enriched Microsoft 365 logs", "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json" } \ No newline at end of file diff --git a/Solutions/Global Secure Access/Workbooks/GSANetworkTraffic.json b/Solutions/Global Secure Access/Workbooks/GSANetworkTraffic.json index 92c9d904ea..e4e50d998a 100644 --- a/Solutions/Global Secure Access/Workbooks/GSANetworkTraffic.json +++ b/Solutions/Global Secure Access/Workbooks/GSANetworkTraffic.json @@ -782,6 +782,6 @@ "fallbackResourceIds": [ "Global Secure Access" ], - "fromTemplateId": "community-Workbooks/Global Secure Access/Common/Network Traffic Insights", + "fromTemplateId": "GSA Network Traffic Insights", "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json" } \ No newline at end of file diff --git a/Workbooks/GSAM365EnrichedEvents.json b/Workbooks/GSAM365EnrichedEvents.json index 461ba0feb5..ea40be7808 100644 --- a/Workbooks/GSAM365EnrichedEvents.json +++ b/Workbooks/GSAM365EnrichedEvents.json @@ -4,15 +4,17 @@ { "type": 1, "content": { - "json": "## Traffic Logs workbook\n---\n\nLog information in the dashboard is limited to 30 days." + "json": "## Enriched Microsoft 365 logs Workbook (Preview)\n---\n\nThe enriched Microsoft 365 logs provide information about Microsoft 365 workloads, so you can review network data and security events relevant to Microsoft 365 apps." }, - "name": "text - 1" + "name": "text - 2" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", - "crossComponentResources": [], + "crossComponentResources": [ + "value::all" + ], "parameters": [ { "id": "ff8b2a55-1849-4848-acf8-eab5452e9f10", @@ -20,24 +22,25 @@ "name": "LogAnalyticWorkspace", "label": "Log Analytic Workspace", "type": 5, - "description": "The log analytic workspace in which to execute the queries", + "description": "The Log Analytic Workspace In Which To Execute The Queries", "isRequired": true, "query": "resources\r\n| where type == \"microsoft.operationalinsights/workspaces\"\r\n| project id", + "crossComponentResources": [ + "value::all" + ], "typeSettings": { "resourceTypeFilter": { "microsoft.operationalinsights/workspaces": true }, - "additionalResourceOptions": [ - "value::1" - ], + "additionalResourceOptions": [], "showDefault": false }, "timeContext": { "durationMs": 86400000 }, - "defaultValue": "value::1", "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources" + "resourceType": "microsoft.resourcegraph/resources", + "value": null }, { "id": "f15f34d8-8e2d-4c39-8dee-be2f979c86a8", @@ -106,7 +109,10 @@ "multiSelect": true, "quote": "'", "delimiter": ",", - "query": "EnrichedMicrosoft365AuditLogsDemos_CL\r\n| summarize Count = count() by UserId_s\r\n| order by Count desc, UserId_s asc\r\n| project Value = UserId_s, Label = strcat(UserId_s, ' - ', Count, ' Logs'), Selected = false", + "query": "EnrichedMicrosoft365AuditLogs\r\n| summarize Count = count() by UserId\r\n| order by Count desc, UserId asc\r\n| project Value = UserId, Label = strcat(UserId, ' - ', Count, ' Logs'), Selected = false", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "typeSettings": { "limitSelectTo": 20, "additionalResourceOptions": [ @@ -128,7 +134,7 @@ ], "style": "pills", "queryType": 1, - "resourceType": "microsoft.operationalinsights/workspaces" + "resourceType": "microsoft.resourcegraph/resources" }, "name": "parameters - 15" }, @@ -137,21 +143,38 @@ "content": { "version": "LinkItem/1.0", "style": "tabs", + "tabStyle": "bigger", "links": [ { - "id": "2b2cd1be-9d25-412c-8444-f005c4789b55", - "cellValue": "tabSel", + "id": "e841bafb-6437-4d29-84ac-ba16c5a6d901", + "cellValue": "selTab", "linkTarget": "parameter", "linkLabel": "Overview", "subTarget": "Overview", "style": "link" }, { - "id": "cc3e67f2-f20f-4430-8dee-d0773b90d9ce", - "cellValue": "tabSel", + "id": "ac5f2082-50bc-4739-bdf2-20c93b613671", + "cellValue": "selTab", + "linkTarget": "parameter", + "linkLabel": "SharePoint/OneDrive Insights", + "subTarget": "Threat", + "style": "link" + }, + { + "id": "dc2778e7-739b-44ba-9ae4-c81901277f57", + "cellValue": "selTab", "linkTarget": "parameter", - "linkLabel": "All Traffic", - "subTarget": "AllTraffic", + "linkLabel": "Exchange Online Insights", + "subTarget": "ThreatEXO", + "style": "link" + }, + { + "id": "666111e2-54ff-4fa4-a648-11a5c8c0235b", + "cellValue": "selTab", + "linkTarget": "parameter", + "linkLabel": "Teams Insights", + "subTarget": "ThreatTeams", "style": "link" } ] @@ -159,77 +182,179 @@ "name": "links - 7" }, { - "type": 3, + "type": 12, "content": { - "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| project \r\n Timestamp = createdDateTime_t,\r\n User = userPrincipalName_s,\r\n SourceIP = SourceIP,\r\n DestinationIP = destinationIp_s,\r\n DestinationPort = destinationPort_d,\r\n Action = action_s,\r\n PolicyName = policyName_s,\r\n TransportProtocol = transportProtocol_s,\r\n TrafficType = trafficType_s,\r\n DestinationURL = destinationUrl_s,\r\n ReceivedBytes = receivedBytes_d,\r\n SentBytes = sentBytes_d,\r\n DeviceOS = deviceOperatingSystem_s,\r\n PolicyRuleID = policyRuleId_s\r\n| order by Timestamp desc", - "size": 3, - "showAnalytics": true, - "title": "Log", - "timeContextFromParameter": "TimeRange", - "showExportToExcel": true, - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "gridSettings": { - "rowLimit": 1000, - "filter": true - } + "version": "NotebookGroup/1.0", + "groupType": "editable", + "items": [ + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| where Workload in (\"Exchange\")\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\r\n| extend \r\n Country = tostring(GeoInfo.country), \r\n State = tostring(GeoInfo.state), \r\n City = tostring(GeoInfo.city), \r\n Latitude = tostring(GeoInfo.latitude), \r\n Longitude = tostring(GeoInfo.longitude)\r\n| project \r\n UserId, \r\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\r\n Latitude, \r\n Longitude, \r\n City, \r\n State, \r\n Country\r\n| summarize Count = count() by City, State, Country\r\n", + "size": 0, + "title": "Access By Location", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "visualization": "map", + "mapSettings": { + "locInfo": "CountryRegion", + "locInfoColumn": "Country", + "latitude": "Latitude", + "longitude": "Longitude", + "sizeSettings": "Count", + "sizeAggregation": "Sum", + "labelSettings": "Country", + "legendMetric": "Count", + "legendAggregation": "Sum", + "itemColorSettings": { + "nodeColorField": "Count", + "colorAggregation": "Sum", + "type": "heatmap", + "heatmapPalette": "turquoise" + } + } + }, + "customWidth": "50", + "name": "query - 0" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| where Workload in (\"Exchange\")\r\n| summarize [\"Count\"] = count() by [\"Source IP\"] = SourceIp, [\"Device Operating System\"] = DeviceOperatingSystem\r\n| order by [\"Count\"] desc\r\n", + "size": 0, + "title": "Access By Location And OS", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "customWidth": "50", + "name": "query - 1" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where Operation in (\"Set-AdminAuditLogConfig\", \"Set-OrganizationConfig\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", + "size": 0, + "title": "Audit Of Critical Configuration Changes", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "customWidth": "50", + "name": "query - 2" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where Operation in (\"Set-TransportRule\", \"Set-AtpPolicyForO365\", \"Set-MalwareFilterRule\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", + "size": 0, + "title": "Changes In Email Security Policies", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "customWidth": "50", + "name": "query - 3" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where Operation in (\"Add-RoleGroupMember\", \"Remove-ManagementRoleAssignment\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", + "size": 0, + "title": "Monitoring Role Group Membership Changes", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "customWidth": "50", + "name": "query - 4" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where Operation in (\"New-TenantAllowBlockListSpoofitems\", \"Remove-TenantAllowBlockListSpoofitems\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", + "size": 0, + "title": "Spoofing Settings Management Activities", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "customWidth": "50", + "name": "query - 5" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where Operation in (\"New-SafeAttachmentPolicy\", \"Set-SafeAttachmentPolicy\", \"Set-SafeAttachmentRule\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", + "size": 0, + "title": "Safe Attachments Policy Changes", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "customWidth": "50", + "name": "query - 6" + } + ] }, "conditionalVisibility": { - "parameterName": "tabSel", + "parameterName": "selTab", "comparison": "isEqualTo", - "value": "AllTraffic" + "value": "ThreatEXO" }, - "name": "query - 6" + "name": "group - 18" }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "// Unique Users\nNetworkAccessDemo_CL\n| extend GeoInfo = geo_info_from_ip_address(SourceIP) // Extend each row with geolocation info\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\n| project SourceIP, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\n| summarize UniqueUsers=dcount(Country)\n| extend snapshot = \"Total Locations\"\n| project col1 = UniqueUsers, snapshot\n\n// Union with Unique Devices\n| union (\n NetworkAccessDemo_CL\n | where userPrincipalName_s in ({Users}) or '*' in ({Users})\n | extend BytesInGB = todouble(sentBytes_d + receivedBytes_d) / (1024 * 1024 * 1024) // Convert bytes to gigabytes\n | summarize TotalBytesGB = sum(BytesInGB)\n | extend snapshot = \"Total Bytes (GB)\"\n | project col1 = tolong(TotalBytesGB), snapshot\n)\n\n// Union with Total Internet Access\n| union (\n NetworkAccessDemo_CL\n | where userPrincipalName_s in ({Users}) or '*' in ({Users})\n | summarize TotalTransactions = count()\n | extend snapshot = \"Total Transactions\"\n | project col1 = TotalTransactions, snapshot\n)\n\n// Union with Total Private Access\n// Order by Snapshot for consistent tile ordering on dashboard\n| order by snapshot", - "size": 4, - "timeContextFromParameter": "TimeRange", + "query": "EnrichedMicrosoft365AuditLogs | where Workload == \"Exchange\" | where Operation in (\"Add-MailboxFolderPermission\", \"Add-RoleGroupMember\", \"New-TransportRule\", \"Remove-ManagementRoleAssignment\", \"Set-TransportRule\") | summarize Count = count(), Users = makeset(UserId) by Operation | order by Count desc", + "size": 0, + "title": "Permission changes and security policy updates", + "timeContext": { + "durationMs": 86400000 + }, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", - "visualization": "tiles", - "tileSettings": { - "titleContent": { - "columnMatch": "snapshot", - "formatter": 1 - }, - "leftContent": { - "columnMatch": "col1", - "formatter": 12, - "formatOptions": { - "palette": "auto" - } - }, - "showBorder": true, - "size": "auto" - }, - "mapSettings": { - "locInfo": "LatLong", - "sizeSettings": "ExistingClients", - "sizeAggregation": "Sum", - "legendMetric": "ExistingClients", - "legendAggregation": "Sum", - "itemColorSettings": { - "type": "heatmap", - "colorAggregation": "Sum", - "nodeColorField": "ExistingClients", - "heatmapPalette": "greenRed" - } - }, - "textSettings": { - "style": "bignumber" - } + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] }, "conditionalVisibility": { - "parameterName": "tabSel", + "parameterName": "seltab", "comparison": "isEqualTo", - "value": "Overview" + "value": "ThreatEXO" }, - "name": "query - 2" + "name": "query - 13" }, { "type": 12, @@ -241,24 +366,62 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| extend BytesInGB = todouble(sentBytes_d + receivedBytes_d) / (1024 * 1024 * 1024) // Convert bytes to gigabytes\r\n| summarize TotalBytesGB = sum(BytesInGB) by bin(createdDateTime_t, 1h), trafficType_s\r\n| order by bin(createdDateTime_t, 1h) asc, trafficType_s asc\r\n| project createdDateTime_t, trafficType_s, TotalBytesGB\r\n", - "size": 2, - "title": "Usage over Time (GB)", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| where Workload in (\"Teams\")\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\r\n| extend \r\n Country = tostring(GeoInfo.country), \r\n State = tostring(GeoInfo.state), \r\n City = tostring(GeoInfo.city), \r\n Latitude = tostring(GeoInfo.latitude), \r\n Longitude = tostring(GeoInfo.longitude)\r\n| project \r\n UserId, \r\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\r\n Latitude, \r\n Longitude, \r\n City, \r\n State, \r\n Country\r\n| summarize Count = count() by City, State, Country\r\n", + "size": 0, + "title": "Access By Location", "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", - "visualization": "barchart" - }, - "conditionalVisibility": { - "parameterName": "tabSel", - "comparison": "isEqualTo", - "value": "Overview" + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "visualization": "map", + "mapSettings": { + "locInfo": "CountryRegion", + "locInfoColumn": "Country", + "latitude": "Latitude", + "longitude": "Longitude", + "sizeSettings": "Count", + "sizeAggregation": "Sum", + "labelSettings": "Country", + "legendMetric": "Count", + "legendAggregation": "Sum", + "itemColorSettings": { + "nodeColorField": "Count", + "colorAggregation": "Sum", + "type": "heatmap", + "heatmapPalette": "turquoise" + } + } }, + "customWidth": "50", "name": "query - 0" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| where Workload in (\"Teams\")\r\n| summarize [\"Count\"] = count() by [\"Source IP\"] = SourceIp, [\"Device Operating System\"] = DeviceOperatingSystem\r\n| order by [\"Count\"] desc\r\n", + "size": 0, + "title": "Access By Location And OS", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "customWidth": "50", + "name": "query - 1" } ] }, - "name": "group - 5" + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "ThreatTeams" + }, + "name": "group - 10" }, { "type": 12, @@ -270,12 +433,15 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIP) // Extend each row with geolocation info\r\n| project createdDateTime_t, SourceIP, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\r\n| where Country != \"\"\r\n| summarize Count = count() by City, State, Country\r\n", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| where Workload in (\"OneDrive\", \"SharePoint\",\"SPO/OneDrive\")\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\r\n| extend \r\n Country = tostring(GeoInfo.country), \r\n State = tostring(GeoInfo.state), \r\n City = tostring(GeoInfo.city), \r\n Latitude = tostring(GeoInfo.latitude), \r\n Longitude = tostring(GeoInfo.longitude)\r\n| project \r\n UserId, \r\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\r\n Latitude, \r\n Longitude, \r\n City, \r\n State, \r\n Country\r\n| summarize Count = count() by City, State, Country\r\n", "size": 0, - "title": "Locations", + "title": "Access By Location", "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "visualization": "map", "mapSettings": { "locInfo": "CountryRegion", @@ -286,6 +452,7 @@ "sizeAggregation": "Sum", "labelSettings": "Country", "legendMetric": "Country", + "numberOfMetrics": 19, "legendAggregation": "Count", "itemColorSettings": { "nodeColorField": "Count", @@ -296,88 +463,190 @@ } }, "customWidth": "50", - "name": "query - 0" + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Threat" + }, + "name": "query - 19" }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where tolower(action_s) == \"allow\" and destinationWebCategory_displayName_s != '' // Filter for allowed traffic\r\n| extend firstCategory = tostring(split(destinationWebCategory_displayName_s, ',')[0]) // Split and get the first category\r\n| summarize Count = count() by firstCategory\r\n| top 10 by Count\r\n", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| where Workload in (\"OneDrive\", \"SharePoint\", \"SPO/OneDrive\")\r\n| summarize [\"Event Count\"] = count() by [\"Source IP\"] = SourceIp, [\"Device Operating System\"] = DeviceOperatingSystem\r\n| order by [\"Event Count\"] desc\r\n", "size": 2, - "title": "Top Allowed Web Categories", + "title": "Access By Location And OS", "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", - "visualization": "piechart" + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "gridSettings": { + "filter": true + } }, "customWidth": "50", - "name": "query - 7" - }, + "name": "query - 20" + } + ] + }, + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Threat" + }, + "name": "group - 20", + "styleSettings": { + "showBorder": true + } + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where Workload == \"Teams\"\r\n| where Operation in (\"MemberAdded\", \"MemberRemoved\", \"TeamDeleted\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", + "size": 0, + "title": "Changes In Team Memberships", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "ThreatTeams" + }, + "customWidth": "50", + "name": "query - 11" + }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "items": [ { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where tolower(action_s) == \"block\" and destinationFQDN_s != '' // Filter for allowed traffic\r\n| summarize Count = count() by destinationFQDN_s\r\n| top 100 by Count", + "query": "let RiskyExtensions = dynamic([\"exe\", \"msi\", \"bat\", \"cmd\", \"com\", \"scr\", \"pif\", \"ps1\", \"vbs\", \"js\", \"jse\", \"wsf\", \"docm\", \"xlsm\", \"pptm\", \"dll\", \"ocx\", \"cpl\", \"app\", \"vb\", \"reg\", \"inf\", \"hta\"]);\r\n\r\nEnrichedMicrosoft365AuditLogs\r\n| where Workload in (\"SPO/OneDrive\")\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| extend [\"File Extension\"] = tostring(parse_json(AdditionalProperties).SourceFileExtension)\r\n| where [\"File Extension\"] in (RiskyExtensions)\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, [\"File Extension\"], Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], [\"File Extension\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], [\"File Extension\"], Operation, Count\r\n", "size": 0, - "title": "Top Blocked Destinations", + "title": "Risky File Operations", "timeContextFromParameter": "TimeRange", "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "gridSettings": { + "formatters": [ + { + "columnMatch": "OperationCount", + "formatter": 4, + "formatOptions": { + "palette": "blue" + } + } + ] + } }, "customWidth": "50", - "name": "query - 5" + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Threat" + }, + "name": "query - 1" }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where tolower(action_s) == \"block\" and destinationWebCategory_displayName_s != '' // Filter for blocked traffic\r\n| extend firstCategory = tostring(split(destinationWebCategory_displayName_s, ',')[0]) // Split and get the first category\r\n| summarize Count = count() by firstCategory\r\n| top 10 by Count\r\n", - "size": 3, - "title": "Top Blocked Web Categories", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where Workload in (\"OneDrive\", \"SharePoint\", \"SPO/OneDrive\")\r\n| where Operation in (\"FileRecycled\", \"FileDownloaded\", \"FileUploaded\", \"FileCreated\", \"File Modified\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", + "size": 0, + "title": "Bulk File Events", "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", - "visualization": "piechart" + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "gridSettings": { + "formatters": [ + { + "columnMatch": "OperationCount", + "formatter": 4, + "formatOptions": { + "palette": "blue" + } + } + ] + } }, "customWidth": "50", - "name": "query - 6" + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Threat" + }, + "name": "query - 8" }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where sentBytes_d > 0\r\n| where tolower(action_s) != \"block\" \r\n| summarize Count = count() , Sent = sum(sentBytes_d), Recived = sum(receivedBytes_d), Total = sum(receivedBytes_d+ sentBytes_d) by destinationFQDN_s\r\n| order by Count desc\r\n", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where Workload in (\"SPO/OneDrive\")\r\n| where Operation == \"FileDeletedFirstStageRecycleBin\"\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by bin(Timestamp, 1h), [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n| where Count > 1\r\n", "size": 0, - "title": "Top Allowed Destinations", + "title": " Bulk File Deletion Operations", "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "gridSettings": { "formatters": [ { - "columnMatch": "Count", - "formatter": 4, - "formatOptions": { - "palette": "magenta" - } - }, - { - "columnMatch": "Recived", - "formatter": 4, + "columnMatch": "FileDeletions", + "formatter": 8, "formatOptions": { - "palette": "turquoise" - } - }, - { - "columnMatch": "Total", - "formatter": 4, - "formatOptions": { - "palette": "pink" + "palette": "blue" } - }, + } + ] + } + }, + "customWidth": "50", + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Threat" + }, + "name": "query - 3" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "let baselinePeriod = 30d;\r\nlet detectionWindow = 1h;\r\nlet downloadThreshold = 5; // Threshold of downloads indicating potential exfiltration\r\n\r\nEnrichedMicrosoft365AuditLogs\r\n| where TimeGenerated >= ago(baselinePeriod)\r\n| where Workload in (\"OneDrive\", \"SharePoint\", \"SPO/OneDrive\")\r\n| where Operation == \"FileDownloaded\"\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by bin(Timestamp, detectionWindow), [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", + "size": 0, + "title": "Bulk File Download", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "gridSettings": { + "formatters": [ { - "columnMatch": "Sent", - "formatter": 4, + "columnMatch": "DownloadCount", + "formatter": 8, "formatOptions": { "palette": "blue" } @@ -386,33 +655,328 @@ } }, "customWidth": "50", + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Threat" + }, + "name": "query - 4" + } + ] + }, + "name": "group - 9" + }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "items": [ + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\r\n| extend \r\n Country = tostring(GeoInfo.country), \r\n State = tostring(GeoInfo.state), \r\n City = tostring(GeoInfo.city), \r\n Latitude = tostring(GeoInfo.latitude), \r\n Longitude = tostring(GeoInfo.longitude)\r\n| project \r\n UserId, \r\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\r\n Latitude, \r\n Longitude, \r\n City, \r\n State, \r\n Country\r\n | where tostring(Country) != \"\"\r\n| summarize Count = count() by City, State, Country\r\n\r\n\r\n", + "size": 0, + "title": "Access By Location", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "visualization": "map", + "mapSettings": { + "locInfo": "CountryRegion", + "locInfoColumn": "Country", + "latitude": "Latitude", + "longitude": "Longitude", + "sizeSettings": "Count", + "sizeAggregation": "Sum", + "minSize": 20, + "labelSettings": "Country", + "legendMetric": "Country", + "numberOfMetrics": 8, + "legendAggregation": "Count", + "itemColorSettings": { + "nodeColorField": "Count", + "colorAggregation": "Sum", + "type": "heatmap", + "heatmapPalette": "turquoise" + } + } + }, + "customWidth": "50", + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Overview" + }, + "name": "query - 5" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| summarize [\"Event Count\"] = count() by [\"Time Generated\"] = TimeGenerated, [\"User\"] = UserId, [\"Source IP\"] = SourceIp, [\"Device Operating System\"] = DeviceOperatingSystem, [\"Workload\"] = Workload\r\n| order by [\"Time Generated\"] desc\r\n", + "size": 0, + "title": "Access By Location And OS", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "gridSettings": { + "rowLimit": 1000, + "filter": true + } + }, + "customWidth": "50", + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Overview" + }, "name": "query - 1" + } + ] + }, + "name": "group - 19" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "let data = EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| project \r\n Operation, \r\n UserId, \r\n Workload, \r\n SourceIp, \r\n DeviceId, \r\n TimeGenerated, \r\n Details = pack_all(),\r\n OS = tostring(DeviceOperatingSystem)\r\n| extend Workload = tostring(Workload)\r\n| extend WorkloadStatus = case(\r\n Workload == \"Exchange\", \"Exchange\",\r\n Workload == \"Teams\", \"Teams\",\r\n Workload == \"SharePoint\", \"SharePoint\",\r\n \"Other\"\r\n);\r\n\r\nlet appData = data\r\n| summarize \r\n TotalCount = count(), \r\n ExchangeCount = countif(Workload == \"Exchange\"), \r\n TeamsCount = countif(Workload == \"Teams\"), \r\n SharePointCount = countif(Workload == \"SharePoint\"), \r\n OtherCount = countif(Workload == \"Other\") \r\n by UserId\r\n| where UserId != ''\r\n| join kind=inner (\r\n data\r\n | make-series Trend = count() default = 0 on TimeGenerated in range({TimeRange:start}, {TimeRange:end}, {TimeRange:grain}) by UserId\r\n | project-away TimeGenerated\r\n ) on UserId\r\n| order by TotalCount desc, UserId asc\r\n| project UserId, TotalCount, ExchangeCount, TeamsCount, SharePointCount, OtherCount, Trend\r\n| serialize Id = row_number();\r\n\r\ndata\r\n| summarize \r\n TotalCount = count(), \r\n ExchangeCount = countif(Workload == \"Exchange\"), \r\n TeamsCount = countif(Workload == \"Teams\"), \r\n SharePointCount = countif(Workload == \"SharePoint\"), \r\n OtherCount = countif(Workload == \"Other\") \r\n by UserId, SourceIp = tostring(SourceIp)\r\n| join kind=inner (\r\n data\r\n | make-series Trend = count() default = 0 on TimeGenerated in range({TimeRange:start}, {TimeRange:end}, {TimeRange:grain}) by UserId, SourceIp\r\n | project-away TimeGenerated\r\n ) on UserId, SourceIp\r\n| order by TotalCount desc, UserId asc\r\n| project UserId, SourceIp, TotalCount, ExchangeCount, TeamsCount, SharePointCount, OtherCount, Trend\r\n| serialize Id = row_number(1000000)\r\n| join kind=inner (appData) on UserId\r\n| project Id, Name = SourceIp, Type = 'Client IP', ['Events Count'] = TotalCount, Trend, ['Exchange Events'] = ExchangeCount, ['Teams Events'] = TeamsCount, ['SharePoint Events'] = SharePointCount, ['Other Count'] = OtherCount, ParentId = Id1\r\n| union (\r\n appData \r\n | project Id, Name = UserId, Type = 'Operating System', ['Events Count'] = TotalCount, Trend, ['Exchange Events'] = ExchangeCount, ['Teams Events'] = TeamsCount, ['SharePoint Events'] = SharePointCount, ['Other Count'] = OtherCount, ParentId = -1\r\n )\r\n| order by ['Events Count'] desc, Name asc\r\n", + "size": 2, + "title": "Activity Log", + "timeContextFromParameter": "TimeRange", + "showRefreshButton": true, + "showExportToExcel": true, + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "gridSettings": { + "formatters": [ + { + "columnMatch": "Id", + "formatter": 5 + }, + { + "columnMatch": "Type", + "formatter": 5 + }, + { + "columnMatch": "Trend", + "formatter": 9, + "formatOptions": { + "palette": "blue" + } + }, + { + "columnMatch": "Other Count", + "formatter": 5 + }, + { + "columnMatch": "ParentId", + "formatter": 5 + }, + { + "columnMatch": "Operation Count", + "formatter": 8, + "formatOptions": { + "palette": "blue" + } + }, + { + "columnMatch": "Exchange Count", + "formatter": 8, + "formatOptions": { + "min": 0, + "palette": "blue" + } + }, + { + "columnMatch": "Teams Count", + "formatter": 8, + "formatOptions": { + "min": 0, + "palette": "purple" + } + }, + { + "columnMatch": "SharePoint Count", + "formatter": 8, + "formatOptions": { + "min": 0, + "palette": "turquoise" + } + }, + { + "columnMatch": "Details", + "formatter": 5, + "formatOptions": { + "linkTarget": "GenericDetails", + "linkIsContextBlade": true + } + } + ], + "rowLimit": 1000, + "filter": true, + "hierarchySettings": { + "idColumn": "Id", + "parentColumn": "ParentId", + "treeType": 0, + "expanderColumn": "Name" + } + } + }, + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Overview" + }, + "name": "query - 6" + }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "items": [ + { + "type": 1, + "content": { + "json": "
\r\nšŸ’” _Click on a segment of the pie chart to explore more details_" + }, + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Overview" + }, + "name": "text - 2" }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where transportProtocol_s != ''\r\n| summarize Count = count() by toupper(transportProtocol_s)\r\n| top 10 by Count\r\n", - "size": 2, - "title": "Protocol Distburion", + "query": "EnrichedMicrosoft365AuditLogs\r\n| extend \r\n OS = coalesce(DeviceOperatingSystem, \"Unknown OS\"),\r\n OSVersion = coalesce(tostring(DeviceOperatingSystemVersion), \"Unknown Version\")\r\n| summarize DeviceCount = count() by OS, OSVersion\r\n| order by DeviceCount desc\r\n", + "size": 3, + "title": "Devices Accessing M365", "timeContextFromParameter": "TimeRange", + "exportParameterName": "Parampie", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "visualization": "piechart" }, "customWidth": "50", - "name": "query - 3" + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Overview" + }, + "name": "query - 6" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| extend \r\n [\"Operating System\"] = coalesce(DeviceOperatingSystem, \"Unknown OS\"),\r\n [\"OS Version\"] = coalesce(tostring(DeviceOperatingSystemVersion), \"Unknown Version\"),\r\n [\"Device ID\"] = coalesce(tostring(DeviceId), \"Unknown DeviceId\")\r\n| where [\"Operating System\"] == dynamic({Parampie}).label\r\n| summarize [\"Device Count\"] = count() by [\"Operating System\"], [\"OS Version\"], [\"Device ID\"]\r\n| order by [\"Device Count\"] desc\r\n", + "size": 2, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "gridSettings": { + "formatters": [ + { + "columnMatch": "$gen_group", + "formatter": 1 + } + ], + "hierarchySettings": { + "treeType": 1, + "groupBy": [ + "OperatingSystem", + "OSVersion" + ], + "expandTopLevel": false + } + } + }, + "customWidth": "50", + "conditionalVisibilities": [ + { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Overview" + }, + { + "parameterName": "Parampie", + "comparison": "isNotEqualTo" + } + ], + "name": "query - 1" } ] }, + "name": "group - 19" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "let BusinessHoursStart = 8; // 8 AM\r\nlet BusinessHoursEnd = 18; // 6 PM\r\n\r\nEnrichedMicrosoft365AuditLogs \r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| extend HourOfDay = hourofday(TimeGenerated)\r\n| where HourOfDay < BusinessHoursStart or HourOfDay > BusinessHoursEnd\r\n| summarize [\"Off-Hour Activities\"] = count() by [\"User ID\"] = UserId, [\"Date\"] = bin(TimeGenerated, 1d), [\"Operation\"]\r\n| order by [\"Off-Hour Activities\"] desc\r\n", + "size": 0, + "title": "Activity Outside Standard Working Hours (8:00 - 18:00)", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "conditionalVisibility": { + "parameterName": "selTab", + "comparison": "isEqualTo", + "value": "Overview" + }, + "name": "query - 14" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "EnrichedMicrosoft365AuditLogs\n| summarize Count = count() by bin(TimeGenerated, 1h)\n| order by TimeGenerated asc\n", + "size": 0, + "title": "Microsoft 365 Transactions", + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "visualization": "unstackedbar" + }, "conditionalVisibility": { - "parameterName": "tabSel", + "parameterName": "selTab", "comparison": "isEqualTo", "value": "Overview" }, - "name": "group - 4" + "name": "query - 2" } ], - "fallbackResourceIds": [], + "fallbackResourceIds": [ + "Global Secure Access" + ], + "fromTemplateId": "GSA Enriched Microsoft 365 logs", "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json" -} +} \ No newline at end of file diff --git a/Workbooks/GSANetworkTraffic.json b/Workbooks/GSANetworkTraffic.json index 46e0e43e6f..e4e50d998a 100644 --- a/Workbooks/GSANetworkTraffic.json +++ b/Workbooks/GSANetworkTraffic.json @@ -4,16 +4,16 @@ { "type": 1, "content": { - "json": "## Traffic Logs workbook\n---\n\nLog information in the dashboard is limited to 30 days." + "json": "## Network Traffic Insights Workbook (Preview)\n---\nInformation in the dashboard is based on log data\n\n\n" }, - "name": "text - 0" + "name": "text - 2" }, { "type": 9, "content": { "version": "KqlParameterItem/1.0", "crossComponentResources": [ - "" + "value::all" ], "parameters": [ { @@ -22,24 +22,25 @@ "name": "LogAnalyticWorkspace", "label": "Log Analytic Workspace", "type": 5, - "description": "The log analytic workspace in which to execute the queries", + "description": "The Log Analytic Workspace In Which To Execute The Queries", "isRequired": true, "query": "resources\r\n| where type == \"microsoft.operationalinsights/workspaces\"\r\n| project id", + "crossComponentResources": [ + "value::all" + ], "typeSettings": { "resourceTypeFilter": { "microsoft.operationalinsights/workspaces": true }, - "additionalResourceOptions": [ - "value::1" - ], + "additionalResourceOptions": [], "showDefault": false }, "timeContext": { - "durationMs": 86400000 + "durationMs": 2592000000 }, - "defaultValue": "value::1", "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources" + "resourceType": "microsoft.resourcegraph/resources", + "value": null }, { "id": "f15f34d8-8e2d-4c39-8dee-be2f979c86a8", @@ -96,7 +97,7 @@ "durationMs": 86400000 }, "value": { - "durationMs": 2592000000 + "durationMs": 604800000 } }, { @@ -108,7 +109,10 @@ "multiSelect": true, "quote": "'", "delimiter": ",", - "query": "EnrichedMicrosoft365AuditLogsDemos_CL\r\n| summarize Count = count() by UserId_s\r\n| order by Count desc, UserId_s asc\r\n| project Value = UserId_s, Label = strcat(UserId_s, ' - ', Count, ' Logs'), Selected = false", + "query": "NetworkAccessTraffic\r\n| summarize Count = count() by UserPrincipalName\r\n| order by Count desc, UserPrincipalName asc\r\n| project Value = UserPrincipalName, Label = strcat(UserPrincipalName, ' - ', Count, ' Logs'), Selected = false", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "typeSettings": { "limitSelectTo": 20, "additionalResourceOptions": [ @@ -126,10 +130,41 @@ "value": [ "value::all" ] + }, + { + "id": "527af4d2-3089-4aa4-9fbb-48ec697db20d", + "version": "KqlParameterItem/1.0", + "name": "WebCategories", + "label": "Web Categories", + "type": 2, + "multiSelect": true, + "quote": "'", + "delimiter": ",", + "query": "NetworkAccessTraffic\r\n| extend firstCategory = tostring(split(DestinationWebCategories, ',')[0]) // Split and get the first category\r\n| summarize Count = count() by firstCategory\r\n| order by Count desc, firstCategory asc\r\n| project Value = firstCategory, Label = strcat(firstCategory, ' - ', Count, ' Logs')", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "typeSettings": { + "additionalResourceOptions": [ + "value::all" + ], + "selectAllValue": "*", + "showDefault": false + }, + "timeContext": { + "durationMs": 604800000 + }, + "timeContextFromParameter": "TimeRange", + "defaultValue": "value::all", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "value": [ + "value::all" + ] } ], "style": "pills", - "queryType": 1, + "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces" }, "name": "parameters - 15" @@ -155,23 +190,286 @@ "linkLabel": "All Traffic", "subTarget": "AllTraffic", "style": "link" + }, + { + "id": "5ae54b5a-ac7b-4b7a-a1e1-1e574625caa3", + "cellValue": "tabSel", + "linkTarget": "parameter", + "linkLabel": "URL Lookup", + "subTarget": "URLLookup", + "style": "link" + }, + { + "id": "68c566f8-957e-4a3f-8b66-730fc24135fb", + "cellValue": "tabSel", + "linkTarget": "parameter", + "linkLabel": "Security Insights", + "subTarget": "Security", + "style": "link" } ] }, "name": "links - 7" }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "items": [ + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "let NetworkAccessTrafficData = NetworkAccessTraffic\r\n| where SourceIp != \"\" and isnotempty(SourceIp)\r\n| summarize arg_max(TimeGenerated, *) by SourceIp, TenantId;\r\n\r\nlet SigninLogsData = SigninLogs\r\n| where IPAddress != \"\" and isnotempty(IPAddress)\r\n| summarize arg_max(TimeGenerated, *) by IPAddress, TenantId, UserId, CorrelationId;\r\n\r\nSigninLogsData\r\n| join kind=leftanti (\r\n NetworkAccessTrafficData\r\n | where SourceIp != \"\" and isnotempty(SourceIp)\r\n) on $left.IPAddress == $right.SourceIp and $left.TenantId == $right.TenantId\r\n| project TimeGenerated, IPAddress, UserId, UserPrincipalName, AppDisplayName, DeviceDetail.deviceId\r\n", + "size": 0, + "title": "Sign-ins Outside Global Secure Access", + "timeContextFromParameter": "TimeRange", + "showExportToExcel": true, + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "gridSettings": { + "rowLimit": 1000, + "filter": true + } + }, + "name": "query - 0" + } + ] + }, + "conditionalVisibility": { + "parameterName": "tabSel", + "comparison": "isEqualTo", + "value": "Security" + }, + "name": "group - 10" + }, + { + "type": 1, + "content": { + "json": "šŸ’” Type the URL for detailed analysis" + }, + "conditionalVisibility": { + "parameterName": "tabSel", + "comparison": "isEqualTo", + "value": "URLLookup" + }, + "name": "text - 10" + }, + { + "type": 12, + "content": { + "version": "NotebookGroup/1.0", + "groupType": "editable", + "items": [ + { + "type": 9, + "content": { + "version": "KqlParameterItem/1.0", + "parameters": [ + { + "id": "ec8c38c8-3064-4921-8dcd-a69d3895599b", + "version": "KqlParameterItem/1.0", + "name": "URL", + "type": 1, + "typeSettings": { + "isSearchBox": true + }, + "timeContext": { + "durationMs": 86400000 + } + } + ], + "style": "above", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces" + }, + "conditionalVisibility": { + "parameterName": "tabSel", + "comparison": "isEqualTo", + "value": "URLLookup" + }, + "name": "parameters - 9" + }, + { + "type": 1, + "content": { + "json": "# Web Categories" + }, + "name": "text - 11" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "let CategoryData = NetworkAccessTraffic \r\n| where DestinationFqdn contains \"{URL}\" // Filters logs based on the provided URL parameter\r\n| extend CategoriesList = split(DestinationWebCategories, \",\") // Split categories into a list\r\n| mv-expand Category = CategoriesList // Expand the list into individual rows\r\n| extend Category = trim_start(\" \", trim_end(\" \", tostring(Category))) // Trim leading and trailing spaces\r\n| extend Category = iff(TrafficType == \"microsoft365\", \"Microsoft M365\", Category) // Set category to \"Microsoft M365\" if TrafficType is \"microsoft365\"\r\n| summarize UniqueCategories = make_set(Category); // Create a set of unique categories\r\n\r\nCategoryData\r\n| extend \r\n PrimaryCategory = iff(array_length(UniqueCategories) > 0, tostring(UniqueCategories[0]), \"None\")\r\n| project \r\n col1 = PrimaryCategory,\r\n snapshot = \"Primary Category\"\r\n\r\n| union (\r\n CategoryData\r\n | extend \r\n SecondaryCategory = iff(array_length(UniqueCategories) > 1, tostring(UniqueCategories[1]), \"None\")\r\n | project \r\n col1 = SecondaryCategory,\r\n snapshot = \"Secondary Category\"\r\n)\r\n\r\n| order by snapshot asc\r\n", + "size": 4, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "visualization": "tiles", + "tileSettings": { + "titleContent": { + "columnMatch": "snapshot" + }, + "leftContent": { + "columnMatch": "col1", + "numberFormat": { + "unit": 17, + "options": { + "style": "decimal", + "maximumFractionDigits": 2 + } + } + }, + "showBorder": true, + "size": "auto" + } + }, + "name": "query - 17" + }, + { + "type": 1, + "content": { + "json": "# Traffic Access Details" + }, + "name": "text - 15" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "let NetworkData = NetworkAccessTraffic\r\n| where DestinationFqdn contains \"{URL}\"; // Filters logs based on the provided URL parameter\r\n\r\nNetworkData\r\n| summarize \r\n UniqueUsers = dcount(UserPrincipalName),\r\n UniqueDevices = dcount(DeviceId), \r\n TotalAllow = countif(Action == \"Allow\"),\r\n TotalBlock = countif(Action == \"Block\")\r\n| project \r\n col1 = UniqueUsers, \r\n snapshot = \"Unique Users\"\r\n| union (NetworkData\r\n | summarize UniqueDevices = dcount(DeviceId)\r\n | project col1 = UniqueDevices, snapshot = \"Unique Devices\")\r\n| union (NetworkData\r\n | summarize TotalAllow = countif(Action == \"Allow\")\r\n | project col1 = TotalAllow, snapshot = \"Total Allow\")\r\n| union (NetworkData\r\n | summarize TotalBlock = countif(Action == \"Block\")\r\n | project col1 = TotalBlock, snapshot = \"Total Block\")\r\n| order by snapshot asc", + "size": 4, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "visualization": "tiles", + "tileSettings": { + "titleContent": { + "columnMatch": "snapshot", + "formatter": 1 + }, + "leftContent": { + "columnMatch": "col1", + "formatter": 12, + "formatOptions": { + "palette": "auto" + }, + "numberFormat": { + "unit": 17, + "options": { + "style": "decimal", + "maximumFractionDigits": 2, + "maximumSignificantDigits": 3 + } + } + }, + "showBorder": true + } + }, + "name": "query - 14" + }, + { + "type": 1, + "content": { + "json": "# Bandwidth Usage" + }, + "name": "text - 16" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "let NetworkData = NetworkAccessTraffic\r\n| where DestinationFqdn contains \"{URL}\"; // Filters logs based on the provided URL parameter\r\n\r\nNetworkData\r\n| summarize \r\n TotalSent = sum(SentBytes),\r\n TotalReceived = sum(ReceivedBytes),\r\n TotalBandwidth = sum(SentBytes + ReceivedBytes)\r\n| extend \r\n TotalSentMB = round(TotalSent / 1024.0 / 1024.0, 2),\r\n TotalReceivedMB = round(TotalReceived / 1024.0 / 1024.0, 2),\r\n TotalBandwidthMB = round(TotalBandwidth / 1024.0 / 1024.0, 2)\r\n| project \r\n TotalSentMB,\r\n TotalReceivedMB,\r\n TotalBandwidthMB\r\n| extend dummy = 1\r\n| project-away dummy\r\n| mv-expand \r\n Column = pack_array(\"TotalSentMB\", \"TotalReceivedMB\", \"TotalBandwidthMB\"),\r\n Value = pack_array(TotalSentMB, TotalReceivedMB, TotalBandwidthMB)\r\n| extend \r\n snapshot = case(\r\n Column == \"TotalSentMB\", \"Total Sent (MB)\",\r\n Column == \"TotalReceivedMB\", \"Total Received (MB)\",\r\n Column == \"TotalBandwidthMB\", \"Total Bandwidth (MB)\",\r\n \"Unknown\"\r\n ),\r\n col1 = iff(Value < 0.01, 0.00, Value)\r\n| project-away Column, Value\r\n| order by snapshot asc", + "size": 4, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "visualization": "tiles", + "tileSettings": { + "titleContent": { + "columnMatch": "snapshot", + "formatter": 1 + }, + "leftContent": { + "columnMatch": "col1", + "formatter": 12, + "formatOptions": { + "palette": "auto" + }, + "numberFormat": { + "unit": 17, + "options": { + "style": "decimal", + "maximumFractionDigits": 2, + "maximumSignificantDigits": 3 + } + } + }, + "showBorder": true + } + }, + "name": "query - 17" + }, + { + "type": 1, + "content": { + "json": "# Traffic Logs (filtered)" + }, + "name": "text - 12" + }, + { + "type": 3, + "content": { + "version": "KqlItem/1.0", + "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| where DestinationFqdn contains \"{URL}\"\r\n| project \r\n Timestamp = TimeGenerated,\r\n User = UserPrincipalName,\r\n [\"Source IP\"] = SourceIp,\r\n [\"Destination IP\"] = DestinationIp,\r\n [\"Destination Port\"] = DestinationPort,\r\n [\"Destination FQDN\"] = DestinationFqdn,\r\n Action = case(\r\n tolower(Action) == \"allow\", \"šŸŸ¢ Allow\", \r\n tolower(Action) == \"block\", \"šŸ”“ Block\", \r\n tolower(Action) // This returns the action in lowercase if it doesn't match \"allow\" or \"block\"\r\n ),\r\n [\"Policy Name\"] = PolicyName,\r\n [\"Policy Rule ID\"] = PolicyRuleId,\r\n [\"Received Bytes\"] = ReceivedBytes,\r\n [\"Sent Bytes\"] = SentBytes,\r\n [\"Device OS\"] = DeviceOperatingSystem \r\n| order by Timestamp desc\r\n", + "size": 0, + "timeContextFromParameter": "TimeRange", + "queryType": 0, + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ] + }, + "name": "query - 13" + } + ] + }, + "conditionalVisibility": { + "parameterName": "tabSel", + "comparison": "isEqualTo", + "value": "URLLookup" + }, + "name": "group - URL Lookup" + }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| project \r\n Timestamp = createdDateTime_t,\r\n User = userPrincipalName_s,\r\n SourceIP = SourceIP,\r\n DestinationIP = destinationIp_s,\r\n DestinationPort = destinationPort_d,\r\n Action = action_s,\r\n PolicyName = policyName_s,\r\n TransportProtocol = transportProtocol_s,\r\n TrafficType = trafficType_s,\r\n DestinationURL = destinationUrl_s,\r\n ReceivedBytes = receivedBytes_d,\r\n SentBytes = sentBytes_d,\r\n DeviceOS = deviceOperatingSystem_s,\r\n PolicyRuleID = policyRuleId_s\r\n| order by Timestamp desc", + "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| project \r\n Timestamp = TimeGenerated,\r\n User = UserPrincipalName,\r\n [\"Source IP\"] = SourceIp,\r\n [\"Destination IP\"] = DestinationIp,\r\n [\"Destination Port\"] = DestinationPort,\r\n [\"Destination FQDN\"] = DestinationFqdn,\r\n Action = case(\r\n tolower(Action) == \"allow\", \"šŸŸ¢ Allow\", \r\n tolower(Action) == \"block\", \"šŸ”“ Block\", \r\n tolower(Action) // This returns the action in lowercase if it doesn't match \"allow\" or \"block\"\r\n ),\r\n [\"Policy Name\"] = PolicyName,\r\n [\"Web Category\"] = DestinationWebCategories,\r\n [\"Transport Protocol\"] = TransportProtocol,\r\n [\"Traffic Type\"] = TrafficType,\r\n [\"Received Bytes\"] = ReceivedBytes,\r\n [\"Sent Bytes\"] = SentBytes,\r\n [\"Device OS\"] = DeviceOperatingSystem,\r\n [\"Policy Rule ID\"] = PolicyRuleId\r\n| order by Timestamp desc\r\n", "size": 3, - "showAnalytics": true, - "title": "Log", + "title": "Traffic Logs", "timeContextFromParameter": "TimeRange", + "showRefreshButton": true, "showExportToExcel": true, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "gridSettings": { "rowLimit": 1000, "filter": true @@ -188,11 +486,14 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "// Unique Users\nNetworkAccessDemo_CL\n| extend GeoInfo = geo_info_from_ip_address(SourceIP) // Extend each row with geolocation info\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\n| project SourceIP, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\n| summarize UniqueUsers=dcount(Country)\n| extend snapshot = \"Total Locations\"\n| project col1 = UniqueUsers, snapshot\n\n// Union with Unique Devices\n| union (\n NetworkAccessDemo_CL\n | where userPrincipalName_s in ({Users}) or '*' in ({Users})\n | extend BytesInGB = todouble(sentBytes_d + receivedBytes_d) / (1024 * 1024 * 1024) // Convert bytes to gigabytes\n | summarize TotalBytesGB = sum(BytesInGB)\n | extend snapshot = \"Total Bytes (GB)\"\n | project col1 = tolong(TotalBytesGB), snapshot\n)\n\n// Union with Total Internet Access\n| union (\n NetworkAccessDemo_CL\n | where userPrincipalName_s in ({Users}) or '*' in ({Users})\n | summarize TotalTransactions = count()\n | extend snapshot = \"Total Trasnacations\"\n | project col1 = TotalTransactions, snapshot\n)\n\n// Union with Total Private Access\n// Order by Snapshot for consistent tile ordering on dashboard\n| order by snapshot", + "query": "// Unique Users\nNetworkAccessTraffic\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Extend each row with geolocation info\n| project SourceIp, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\n| summarize UniqueUsers=dcount(Country)\n| extend snapshot = \"Total Locations\"\n| project col1 = UniqueUsers, snapshot\n\n// Union with Unique Devices\n| union (\n NetworkAccessTraffic\n | where UserPrincipalName in ({Users}) or '*' in ({Users})\n | extend BytesInGB = todouble(SentBytes + ReceivedBytes) / (1024 * 1024 * 1024) // Convert bytes to gigabytes\n | summarize TotalBytesGB = sum(BytesInGB)\n | extend snapshot = \"Total Bytes (GB)\"\n | project col1 = tolong(TotalBytesGB), snapshot\n)\n\n// Union with Total Internet Access\n| union (\n NetworkAccessTraffic\n | where UserPrincipalName in ({Users}) or '*' in ({Users})\n | summarize TotalTransactions = count()\n | extend snapshot = \"Total Transactions\"\n | project col1 = TotalTransactions, snapshot\n)\n\n// Union with Total Private Access\n// Order by Snapshot for consistent tile ordering on dashboard\n| order by snapshot", "size": 4, "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "visualization": "tiles", "tileSettings": { "titleContent": { @@ -243,12 +544,15 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| extend BytesInGB = todouble(sentBytes_d + receivedBytes_d) / (1024 * 1024 * 1024) // Convert bytes to gigabytes\r\n| summarize TotalBytesGB = sum(BytesInGB) by bin(createdDateTime_t, 1h), trafficType_s\r\n| order by bin(createdDateTime_t, 1h) asc, trafficType_s asc\r\n| project createdDateTime_t, trafficType_s, TotalBytesGB\r\n", + "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| extend BytesIn = todouble(SentBytes + ReceivedBytes) / (1024 * 1024) // Convert bytes to Mbytes\r\n| summarize TotalBytesMB = sum(BytesIn) by bin(TimeGenerated, 1h), TrafficType\r\n| order by bin(TimeGenerated, 1h) asc, TrafficType asc\r\n| project TimeGenerated, TrafficType, TotalBytesMB\r\n", "size": 2, - "title": "Usage over Time (GB)", + "title": "Usage Over Time (MB)", "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "visualization": "barchart" }, "conditionalVisibility": { @@ -272,12 +576,15 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIP) // Extend each row with geolocation info\r\n| project createdDateTime_t, SourceIP, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\r\n| where Country != \"\"\r\n| summarize Count = count() by City, State, Country\r\n\r\n", - "size": 0, + "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Extend each row with geolocation info\r\n| project TimeGenerated, SourceIp, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\r\n| where Country != \"\"\r\n| summarize Count = count() by City, State, Country\r\n\r\n", + "size": 3, "title": "Locations", "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "visualization": "map", "mapSettings": { "locInfo": "CountryRegion", @@ -297,19 +604,21 @@ } } }, - "customWidth": "50", "name": "query - 0" }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where tolower(action_s) == \"allow\" and destinationWebCategory_displayName_s != '' // Filter for allowed traffic\r\n| extend firstCategory = tostring(split(destinationWebCategory_displayName_s, ',')[0]) // Split and get the first category\r\n| summarize Count = count() by firstCategory\r\n| top 10 by Count\r\n", + "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| where tolower(Action) == \"allow\" and DestinationWebCategories != '' // Filter for allowed traffic\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| extend firstCategory = tostring(split(DestinationWebCategories, ',')[0]) // Split and get the first category\r\n| summarize Count = count() by firstCategory\r\n| top 10 by Count\r\n", "size": 2, "title": "Top Allowed Web Categories", "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "visualization": "piechart" }, "customWidth": "50", @@ -319,41 +628,79 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where tolower(action_s) == \"block\" and destinationFQDN_s != '' // Filter for allowed traffic\r\n| summarize Count = count() by destinationFQDN_s\r\n| top 100 by Count", - "size": 0, - "title": "Top Blocked Destinations", + "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| where tolower(Action) == \"block\" and DestinationWebCategories != '' // Filter for blocked traffic\r\n| extend firstCategory = tostring(split(DestinationWebCategories, ',')[0]) // Split and get the first category\r\n| summarize Count = count() by firstCategory\r\n| top 10 by Count\r\n", + "size": 3, + "title": "Top Blocked Web Categories", "timeContextFromParameter": "TimeRange", "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" + "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "visualization": "piechart" }, "customWidth": "50", - "name": "query - 5" + "name": "query - 6" }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where tolower(action_s) == \"block\" and destinationWebCategory_displayName_s != '' // Filter for blocked traffic\r\n| extend firstCategory = tostring(split(destinationWebCategory_displayName_s, ',')[0]) // Split and get the first category\r\n| summarize Count = count() by firstCategory\r\n| top 10 by Count\r\n", - "size": 3, - "title": "Top Blocked Web Categories", + "query": "NetworkAccessTraffic \r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| where tolower(Action) == \"block\" and DestinationFqdn != '' // Filter for blocked traffic with non-empty Destination FQDN\r\n| summarize Count = count() by [\"Destination FQDN\"] = DestinationFqdn, [\"Destination Web Categories\"] = DestinationWebCategories, [\"Policy Name\"] = PolicyName\r\n| order by Count\r\n", + "size": 0, + "title": "Top Blocked Destinations", "timeContextFromParameter": "TimeRange", + "showRefreshButton": true, + "showExportToExcel": true, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", - "visualization": "piechart" + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], + "gridSettings": { + "formatters": [ + { + "columnMatch": "Count", + "formatter": 4, + "formatOptions": { + "palette": "blue" + } + } + ], + "rowLimit": 1000, + "filter": true, + "sortBy": [ + { + "itemKey": "$gen_bar_Count_3", + "sortOrder": 2 + } + ] + }, + "sortBy": [ + { + "itemKey": "$gen_bar_Count_3", + "sortOrder": 2 + } + ] }, "customWidth": "50", - "name": "query - 6" + "name": "query - 5" }, { "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where sentBytes_d > 0\r\n| where tolower(action_s) != \"block\" \r\n| summarize Count = count() , Sent = sum(sentBytes_d), Recived = sum(receivedBytes_d), Total = sum(receivedBytes_d+ sentBytes_d) by destinationFQDN_s\r\n| order by Count desc\r\n", + "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| where SentBytes > 0\r\n| where tolower(Action) != \"block\"\r\n| summarize \r\n Count = count(), \r\n [\"Sent Bytes\"] = sum(SentBytes), \r\n [\"Received Bytes\"] = sum(ReceivedBytes), \r\n [\"Total Bytes\"] = sum(ReceivedBytes + SentBytes) \r\n by [\"Destination FQDN\"] = DestinationFqdn\r\n| order by Count desc\r\n", "size": 0, "title": "Top Allowed Destinations", "timeContextFromParameter": "TimeRange", + "showRefreshButton": true, + "showExportToExcel": true, "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "gridSettings": { "formatters": [ { @@ -384,8 +731,22 @@ "palette": "blue" } } + ], + "rowLimit": 1000, + "filter": true, + "sortBy": [ + { + "itemKey": "$gen_bar_Count_1", + "sortOrder": 2 + } ] - } + }, + "sortBy": [ + { + "itemKey": "$gen_bar_Count_1", + "sortOrder": 2 + } + ] }, "customWidth": "50", "name": "query - 1" @@ -394,12 +755,15 @@ "type": 3, "content": { "version": "KqlItem/1.0", - "query": "NetworkAccessDemo_CL\r\n| where userPrincipalName_s in ({Users}) or '*' in ({Users})\r\n| where transportProtocol_s != ''\r\n| summarize Count = count() by toupper(transportProtocol_s)\r\n| top 10 by Count\r\n", + "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where TransportProtocol != ''\r\n| summarize Count = count() by toupper(TransportProtocol)\r\n| top 10 by Count\r\n", "size": 2, - "title": "Protocol Distburion", + "title": "Protocol Distribution", "timeContextFromParameter": "TimeRange", "queryType": 0, "resourceType": "microsoft.operationalinsights/workspaces", + "crossComponentResources": [ + "{LogAnalyticWorkspace}" + ], "visualization": "piechart" }, "customWidth": "50", @@ -415,7 +779,9 @@ "name": "group - 4" } ], - "fallbackResourceIds": [ + "fallbackResourceIds": [ + "Global Secure Access" ], + "fromTemplateId": "GSA Network Traffic Insights", "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json" } \ No newline at end of file diff --git a/Workbooks/WorkbooksMetadata.json b/Workbooks/WorkbooksMetadata.json index 4b96290dfc..d00f2276a3 100644 --- a/Workbooks/WorkbooksMetadata.json +++ b/Workbooks/WorkbooksMetadata.json @@ -8017,7 +8017,7 @@ "GSATrafficLogsWhite.png", "GSATrafficLogsBlack.png" ], - "version": "1.0.0", + "version": "1.0.1", "title": "Microsoft Global Secure Access Traffic Logs", "templateRelativePath": "GSANetworkTraffic.json", "subtitle": "", @@ -8035,7 +8035,7 @@ "GSAEnrichedLogsWhite.png", "GSAEnrichedLogsBlack.png" ], - "version": "1.0.0", + "version": "1.0.1", "title": "Microsoft Global Secure Access Enriched M365 Logs", "templateRelativePath": "GSAM365EnrichedEvents.json", "provider": "Microsoft" From eb842a24aa17c79dfa05721d49dbcfe8c217b2a1 Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Sun, 6 Oct 2024 14:25:49 +0300 Subject: [PATCH 17/27] Connectors --- ... to Team and immediately uploads file.yaml | 3 + ...365 - ExternalUserAddedRemovedInTeams.yaml | 3 + ... Mail_redirect_via_ExO_transport_rule.yaml | 3 + .../Office 365 - Malicious_Inbox_Rule.yaml | 3 + .../Office 365 - MultipleTeamsDeletes.yaml | 3 + .../Office 365 - Office_MailForwarding.yaml | 3 + ...ice 365 - Office_Uploaded_Executables.yaml | 3 + .../Office 365 - RareOfficeOperations.yaml | 3 + ...ce 365 - SharePoint_Downloads_byNewIP.yaml | 3 + ...- SharePoint_Downloads_byNewUserAgent.yaml | 3 + ...ffice 365 - exchange_auditlogdisabled.yaml | 3 + .../Office 365 - office_policytampering.yaml | 3 + ...repoint_file_transfer_above_threshold.yaml | 11 +- ...file_transfer_folders_above_threshold.yaml | 11 +- .../Data/Solution_GlobalSecureAccess.json | 2 +- .../Global Secure Access/Package/3.0.0.zip | Bin 48426 -> 0 bytes .../Global Secure Access/Package/3.1.0.zip | Bin 0 -> 48462 bytes .../Package/mainTemplate.json | 476 ++++++++++-------- 18 files changed, 331 insertions(+), 205 deletions(-) delete mode 100644 Solutions/Global Secure Access/Package/3.0.0.zip create mode 100644 Solutions/Global Secure Access/Package/3.1.0.zip diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - External User added to Team and immediately uploads file.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - External User added to Team and immediately uploads file.yaml index e9fa0d76fe..8481644cb7 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - External User added to Team and immediately uploads file.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - External User added to Team and immediately uploads file.yaml @@ -8,6 +8,9 @@ requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs + - connectorId: Office365 + dataTypes: + - OfficeActivity (Teams) queryFrequency: 1h queryPeriod: 1h triggerOperator: gt diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - ExternalUserAddedRemovedInTeams.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - ExternalUserAddedRemovedInTeams.yaml index c2220e61a0..010189337f 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - ExternalUserAddedRemovedInTeams.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - ExternalUserAddedRemovedInTeams.yaml @@ -8,6 +8,9 @@ requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs + - connectorId: Office365 + dataTypes: + - OfficeActivity (Teams) queryFrequency: 1h queryPeriod: 1h triggerOperator: gt diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml index eabfa1226a..d6a9ebb224 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml @@ -9,6 +9,9 @@ requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs + - connectorId: Office365 + dataTypes: + - OfficeActivity (Exchange) queryFrequency: 1h queryPeriod: 1h triggerOperator: gt diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Malicious_Inbox_Rule.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Malicious_Inbox_Rule.yaml index 2dd383aec3..d73d02c23e 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Malicious_Inbox_Rule.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Malicious_Inbox_Rule.yaml @@ -10,6 +10,9 @@ requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs + - connectorId: Office365 + dataTypes: + - OfficeActivity (Exchange) queryFrequency: 1d queryPeriod: 1d triggerOperator: gt diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - MultipleTeamsDeletes.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - MultipleTeamsDeletes.yaml index 789f90df15..0628b94341 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - MultipleTeamsDeletes.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - MultipleTeamsDeletes.yaml @@ -9,6 +9,9 @@ requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs + - connectorId: Office365 + dataTypes: + - OfficeActivity (Teams) queryFrequency: 1d queryPeriod: 1d triggerOperator: gt diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_MailForwarding.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_MailForwarding.yaml index 1ab3097e01..cfcf0a9daa 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_MailForwarding.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_MailForwarding.yaml @@ -9,6 +9,9 @@ requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs + - connectorId: Office365 + dataTypes: + - OfficeActivity (Exchange) queryFrequency: 1d queryPeriod: 7d triggerOperator: gt diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_Uploaded_Executables.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_Uploaded_Executables.yaml index 954d1961c1..31eae6d848 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_Uploaded_Executables.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Office_Uploaded_Executables.yaml @@ -11,6 +11,9 @@ requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs + - connectorId: Office365 + dataTypes: + - OfficeActivity (SharePoint) queryFrequency: 1d queryPeriod: 8d triggerOperator: gt diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - RareOfficeOperations.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - RareOfficeOperations.yaml index 54d0316344..862c72ef2a 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - RareOfficeOperations.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - RareOfficeOperations.yaml @@ -8,6 +8,9 @@ requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs + - connectorId: Office365 + dataTypes: + - OfficeActivity queryFrequency: 1d queryPeriod: 1d triggerOperator: gt diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewIP.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewIP.yaml index b5b02f5145..aa21e5cc0a 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewIP.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewIP.yaml @@ -8,6 +8,9 @@ requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs + - connectorId: Office365 + dataTypes: + - OfficeActivity (SharePoint) queryFrequency: 1d queryPeriod: 14d triggerOperator: gt diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml index c9d57bdca8..04ab14db07 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - SharePoint_Downloads_byNewUserAgent.yaml @@ -8,6 +8,9 @@ requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs + - connectorId: Office365 + dataTypes: + - OfficeActivity (SharePoint) queryFrequency: 1d queryPeriod: 14d triggerOperator: gt diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - exchange_auditlogdisabled.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - exchange_auditlogdisabled.yaml index 9ac9b79f19..8136e26684 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - exchange_auditlogdisabled.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - exchange_auditlogdisabled.yaml @@ -8,6 +8,9 @@ requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs + - connectorId: Office365 + dataTypes: + - OfficeActivity (Exchange) queryFrequency: 1d queryPeriod: 1d triggerOperator: gt diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - office_policytampering.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - office_policytampering.yaml index 74ca0aa0cb..ee57a2ba62 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - office_policytampering.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - office_policytampering.yaml @@ -10,6 +10,9 @@ requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs + - connectorId: Office365 + dataTypes: + - OfficeActivity (Exchange) queryFrequency: 1d queryPeriod: 1d triggerOperator: gt diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml index f9a4994748..4d072b9dde 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml @@ -1,14 +1,17 @@ id: 30375d00-68cc-4f95-b89a-68064d566358 name: GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold description: | - Identifies Office365 Sharepoint File Transfers above a certain threshold in a 15-minute time period. - Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur. + 'Identifies Office365 Sharepoint File Transfers above a certain threshold in a 15-minute time period. + Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur.' severity: Medium status: Available requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs + - connectorId: Office365 + dataTypes: + - OfficeActivity (SharePoint) queryFrequency: 15m queryPeriod: 15m triggerOperator: gt @@ -71,7 +74,7 @@ entityMappings: - identifier: Name columnName: FileSample customDetails: - TransferCount: count_distinct_ObjectId + TransferCount: count_distinct_OfficeObjectId FilesList: fileslist incidentConfiguration: createIncident: true @@ -84,5 +87,5 @@ incidentConfiguration: - Account groupByAlertDetails: [] groupByCustomDetails: [] -version: 2.0.7 +version: 1.0.5 kind: Scheduled diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml index 81410ac5c8..500b037a4b 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml @@ -1,13 +1,16 @@ id: abd6976d-8f71-4851-98c4-4d086201319c name: GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold description: | - Identifies Office365 Sharepoint File Transfers with a distinct folder count above a certain threshold in a 15-minute time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur. + 'Identifies Office365 Sharepoint File Transfers with a distinct folder count above a certain threshold in a 15-minute time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur.' severity: Medium status: Available requiredDataConnectors: - connectorId: AzureActiveDirectory dataTypes: - EnrichedMicrosoft365AuditLogs + - connectorId: Office365 + dataTypes: + - OfficeActivity (SharePoint) queryFrequency: 15m queryPeriod: 15m triggerOperator: gt @@ -68,17 +71,17 @@ entityMappings: - identifier: Name columnName: FileSample customDetails: - TransferCount: count_distinct_ObjectId + TransferCount: count_distinct_OfficeObjectId FilesList: fileslist incidentConfiguration: createIncident: true groupingConfiguration: enabled: true reopenClosedIncident: false - lookbackDuration: 5h + lookbackDuration: PT5H matchingMethod: Selected groupByEntities: - - Account + - Account groupByAlertDetails: [] groupByCustomDetails: [] version: 2.0.7 diff --git a/Solutions/Global Secure Access/Data/Solution_GlobalSecureAccess.json b/Solutions/Global Secure Access/Data/Solution_GlobalSecureAccess.json index 4f92d581f0..990dd54b3c 100644 --- a/Solutions/Global Secure Access/Data/Solution_GlobalSecureAccess.json +++ b/Solutions/Global Secure Access/Data/Solution_GlobalSecureAccess.json @@ -56,7 +56,7 @@ "Hunting Queries/MultipleUsersEmailForwardedToSameDestination.yaml" ], "BasePath": "C:\\git\\Azure-Sentinel\\Azure-Sentinel\\Solutions\\Global Secure Access", - "Version": "3.0.0", + "Version": "3.1.0", "Metadata": "SolutionMetadata.json", "TemplateSpec": true, "StaticDataConnectorIds": [ diff --git a/Solutions/Global Secure Access/Package/3.0.0.zip b/Solutions/Global Secure Access/Package/3.0.0.zip deleted file mode 100644 index 6df173c9523aba092dc9c606146005040b430f97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48426 zcmY(JQKtNDHcPio<>_tXS>X<-4ym3H482?s{983(HOjIpIO-wCpESxNCZRjl>ZEdb} zE*v(*Q@(ptKM-^>vQf_V#gx1v0olKdxMC{2=G3J>2OQc2^Q4}k1dV3>%*iwVdZ&0d zdMCV1pAAPB;ZleM;L<27$3U@SZuKI&bG?=TCI2t4SM5|BD-N_f~yRPjjvyn8FpDpNO+9tli!Pb<$Y z=-Y{o`Ei&%aGmPpgffx7@hJaP!QmT&Ai3^X4g`47DF?=s*&mo(yX+V1lCrQ0N9vuT z(7$u%lvy|frzW{q_&m=8VNmFr8`pHQJV)IFem@KeVhgBB9dCc`P-nm-zQ;1ioEL3- zF{-Z75}b&axEYE6B3{3hz3lmN)K;m33nfH{;PT2ZU56_j#!0(4JMvGfAk55PfqY4b zedS8Dj(EIu6`Jvcme8c7#>fo4vmaAdD;Ta;m0<+CgO_A1+z1nm(4Ve+< zd+zVa!OfN-n7SJoeJN%Vc|rFXBZO&ZB^5GiPm_Y1 z5IWvF`dQ=AC2b;#t&T$%J#lmxlUy z;2}TGlKE^Nv+~2xD8sP%p_7z^=2#U`QJs}PWqwI{%V_9Cu_9M`cUJ(0$+zGYYQcuP z0xsUpdpAPqaH5=Hjq~u6(x^)wSDz@*^blCh_rFuYk7jicbH7Z%#o;#z8L%G^jEN8Y zO1cJ>HLmXPmDf(o(h%~D36ko5Z=rgTVM6SJL;V$fC3v42l8bMnY7;EsJlu`E=`0*Xqg%MUumRpCl6F~-gV{VJ zkIkb=4BLnKhZ7@u3r_?NAnPihbg5J&eOIyRkh(}}S0Dv9ycKgzaX>V*eU$XI8)!cy zYMxZ!L!|`tW?1GufZ{IHX>x*+s0~BPRJ>_eIW!myfEcSXdazm(jQUVXnMCb{y9v~u=}gfT1X#~f31P!G2<0afy{Kd|0`R3@5wEpDOq)Wk~ zI^g(m3ll*_TpGx9b!J-FP~-uOs}kOl^Wr;k=V}=WH#a4flEv8y!wXi+7D#mqc(ZUF zU4_>l;uxgkM8V7c#wAkD+-}d01>v%k`fE`;VK1Qg+1!r%V$`R2%dLBkSf^SHir%H~ zeEeM8T;~LQmjrkOhP@YREidJ&S-!_0{Bw#yI>)R_V^k^;;uo_6)e=)Hg+$QyCM6=1 z@z~i9?}CV}drLBuP`AT=DmiWa-BOvPE(Ns^;n>8JH$g#t_~rYPm1ekDpQ4`hTYPR9 z?ouNl^+s!jj;qFg1J`~nr#W_4?fZ8em2$giC5dMkr%cW-wf9>&l+{&3dKmEB3b@4A zkQtrvc%cOYdh~Qt42bwT__)u6md1m>p9c4igl_KrB@sU>mvH+tY~KJ+)4(_>WP(r0nL&I}i;y^+- zh8{bX&Q6^3s`!3zK7zRCGn4nilYmW|Cu*;$rMkwVF+Z}rF=?$W+ZYePHh#yb3?xgi zPNblTZd{+@f$LTMr76BvJpFJf-M@nu<=PX51c&@BT#JW93YSC|zcd9j!HYGQ3etaLO3=9eszDPvNg%y@_EQD9`uUJj=7EWA8}5#X*&>olzv zk*eSLTBuMa)3sfnROfk{F*dKSuNQSUu+F_HhhGsM&>lt**XvVKO(qIkR2P5`+fi9( zv=Vg5tjTXSX7F3b+H>p;4`8#HUNtY2+HvgS{`WivkuuA4OjL_`x;`sq8}MpxAj~#SyabiLL{4k-)m)noKrmF>P^&ejt+dk8n9wxZ{3QYjZBK)U$H}np#lNZ*h^S zjqD>%D&UFF*51nM>vR)gDAxv0BTEVBhz2P`IWQvXI8{T~*Ypq6-!0A5J#>N1_!9TO^=J||H9m#CP@ZA`Q?RNl1Dln0CjX| z8K}~AX!hgm&ZlufULjuw+{h0g+7Wey z6?f|9;;;927jEeaSuT$voUJ_ZLjg0L-J=8uo%yx6K(RZ36codwlO#mVapx(UyAjLU zpd8U;XF6QiVD-Fcq6FK7o4@4Ir5bMLo#03a$K7O&xdIo+QNo)aKy|k;K#>nkt=m>+ z7GM4)V`R05qqPir$lix)t?pe1X4Zuu=1P@n17JzLXbH{Bv_1i40^Jdu+OCX)HtQz7 zP_ZAnoo77!gmY=|Ah6F?zyt z#48U4kI<7O{LXqEbC*Hp&5dWC7D+Y9@fFL=b0nZ54|m3l7eRIERgL7J>DJcQ=uJ8N z(H#S~ip~=yftEzkg6NO_fKmmUnsdx?(FO`SSQi9ofp@&YT--;>XHcUDSVQddjN3N2 zdkqIF%W^l2@QZUPx%67WDTQD|Jcc;HNKp!)1m~LZ=OXrkE10g0BqB`py9pKT6ZzaH zAQEWPC6L%cl?UY#pg4gm@_z09Qhy-Q^eQ8EBp_@Tm}Cku=;*w!#nYu43{Y*S#Z@ou zr}m?i*SE^@#=rhTm??=Wje%d|-CiCOYiiUO{_Rw327#c8y zrLd3LxIhC*bwTKpba9qy@;5JoTDGJz_StDExBI18#rN=`8e*c@P4l$(T84JpPGGhi z?BU-PCgPv@;zw`=DvBmtP@B_Wm~r$i?}MAErqdqcVd!KJv79X#G4uFZgPdb$U&#r|;msKpw}>~M?}Yu16j)f~-y7`xF4&1PFuQK$jo4sh;@?eimy zI1M{eb=yw^HkIom8bovuMzUsi{rz$~{FxSnJrQgcVK;i5e&}X<*jjnoTTSWFmAvLI z1PQbn4{4Zydw2VDbo)n0k)MKNIb9svA6{$lo>dG1v-Tr^`FGWIJki(UQaq*yL9iD& zAJdlx85EGjZGTKY+{4BgsQ)Gy%#~l;ctnVuzgY^rBGXXj{``q)=mqiQuJA<}shOBn zTNF0_a{U-A--s%VY7SGXC8Q41L&=l2a||6J2S_G{+02>`m4;y{e&a!+TI(@aoCjO)EK{Abfy^=P1V~7Bh+r_XaahpaEVzu(79)L6NS## zEGZ&ZZZO*hyVW|h@RBH))CvqCt!rB)bSG%WSC|gs=jW#t?C4tLtxCFj@DN2^e*%Ek z44+c4jx=l~Dw@UE$e<&HhhUu{9G4WJm_t_!7Jpta$k?H7s96Wuq>sNq@FXj^>}quB z{KQBZ`k2olvTw9-f&_MkcR5;^<{xawRf&HwhFjs+4A!XmBTRK}6_eJ)0ZLnJ1YL%N zLa&qq`wKhPa`7+M9v{Way7y1dXx)=$E|6NAN%rbMnj>rDGEgxLbaD1 za{wc7$uJ$nN}j9tk$R(yw&c*Is4}$+lQYL!(X-$V(|xu) zABUK}qplJTo17GF3H`c%euE-KH?lc$nh)M%0F%;0t0S+;Q)VrgW4lMRk0I0$Q$4mp_v+xW}nV*a?EDv>> z=)3W7tELFE4Ck{(sbuoQ*n9rfaA0V1{STKt^+3-r-ZCdPeu!Y+QudM6iV+|fpXR%u z@evY0+weV!DwdX5*Pr1mYijfluiPw!0>Z1ICP7iOE-`hsMPod)x~L1@C?-P;p5tAx zy{^BevuBHuOEWI7MzzQg94TJ62vvd6s`7CL%%%PKF@TmSTtto;Tr)d1$B?Bp*da_^@{5no0a|zRNDW`DYy}#?t+H|d=8w^dxvvQ8GPd;oQ^y|=1F{?-BfsVA(r?YiXah zvJPfiy|DbAL2d)bRO@FP!9SYZb`JfPMgV1&&?V5nC2q6Uh|c6Xi!=QP4)e&lNDkxW z3*0*ViV98K~4T^ns{k)qRsq41wpWF zl-I_8Fn|g3mJ8Hz2l{nV2jK{}VZe62?*F=BFKj>-#udtB(jH*hHv65h?{;QG-MDgB zgy)+s|ES$|9Z-*un{Ljbp}&saYyF0!(bw3iCu@ApnluH!N`)VG5l*i>6HkKq@<4#( zQfvhm-QH3DTK)_5fVGLKtH|R5YLA)U0uqMUJql7c4EORYMvY4cB?67vwm?zG#xywF zgC=iXp6_lN1*AoO}X)`E!AHZ;VzwXuKrZA&?8QhPDg3cRlac4Q6a;a%uY zZjqc=QvEi$%AflZ!%XI)%XC@ZJQ&@~!>i!YmO3^wlQB3Mt4sp`e~Y~76_FGmLe~cL zrctZ%tzJ!Emh~!p%9j#|%iT>W3!U zQDtgo8X@LY2!4up8hK6*QXX1v2St34lS&E!%PL~X+Iy=jQnoJZz;wedoQK9Yc+7zQ z$Y(FLcNFmh#-53^(-Y$8k^bTY&?V=G{)9%(%UHMQh1c8H`8%)04cpuqR~pQMY{6rS z#@h!I*x*Qn=@yIp%UW6IHPN*!_xn=1dOGwq`@DyH#;|+NkmbhGtzd~Q z4ty@7<>^`c@HI~S17+5IB&BF*`h#LrEdgcE2%Av99plf$EIATxgxG|Xw)4m_ktK|i zLuT&@gs7IBd|Q+a839TWv$Y3%_znO27?ON#EwLD-@8>^xQNKfTAL=9zefJkYBf~tM zt$LiFCWWY;*%}-~hUxy4%_nO_+c=5K%<07Es2W!xU0#h?SWj~@gH>ORC~N0rM%0wg z8jNGK;-c$R{pHCb@xpoC`<|YmP_N3{In=&CLMfV2Sh6p=l(2#*^6IY>AK9w|QDK32jbv+v8%VZfZHCv{bfHPg{f9Jj zQaI+qOOi@FvQ;deh0q+nyWXE|#$TqLNOhiIc;ghTd_9n_IzVBIRIfq#X#}lUBZTR_5h*!h4B+$o*XUi!hqhJCr zfc8-+oo$H$0luWzheNS^j5|KnX%wDtVS1HioFjXnd{Q^+^keUH*1)xQg!U;2u!0ub zL!?@F(5+|4o$e#V)7T-PAcP@7(+aq4xzj+LQOc&szV-v!xq3bn&?iXWhi+e`(bYqE zsg3;EYg(-*@uB)+qCB3x>aNN2nN8EUD_k&FdY-Ue?!4;KA}pc$?)&+HR`~zSi0@Tr zzr{izpto=cAmo2$gtdW%jf#o2oz*`};(u(2%dQKT4as`?*Plw~ZD6gX22s^Sls=vc z&Fd7p4(SCKsSTwoZZi%3b;7u$S#n@@A!)UxXP+>Q2j7}>vouJaSVm-bU=+as$=d>0 zaMzl8Xk&6_5)xt-lJ2P2gPtFbfz>x<{hheFriZ{fN0v;v00IPr)x@v5BaIbf+0M1@ zrw=dbXKUZd9dO9#_O+TNt)_PO-MF#Qo)HHOV_qx`minLA-k0u;@_EG7ZjM0LAMq0Z z3(r?P$DWvY) z<88H8RUV=6r>`l%dQc}zs$J>Y{fz9pUIsgIwtBT#JTBw%*$Ae-r?96ix_&F8ygro6 z`DDC5NSWB`c^x%uszuOuR`uqzqi&aG>wTs(A%-clK|G}IA@3uNq`eIf!hj59!~h!B zFbG8$S-!vcek89xhjU}IuvMzM$eVJj%8fo0u6ADF4>$~4bs0%g0;{kaa11{`vIV|S zWN9-OMLB-9T088oa0cMvLS?tKUJvcgV=eApB*fMIl`ww1jDGZkLWGJk7Yl6@;)2JX zpj(mo)z`ixtEmECA&m4$ka$Us$r&ws9l_c_u%JdLxM5=@28VsG)JUq!FC$($JUw$C zM(}a&1L92_GneChQFmB)PbrgwUcQ^`4>bxu(gBc+sbGgh35||OBr7B&S=)Y-$BDrr zSg+q04Iy$29Kmz0s`nP1PbIw;z4`PUvogG(3GhDV=QB6{N(lIkFa0QvADc&glSie-$)dUsuLb=Z zSM#{^f#lGlZ`aoL>^!-005+6nI9z!R{>}Vn5O(t-Q^Srh03Va`do_2ttx0^3P-H4XWd^hC?}(R#6Z=zk8=gxRs+FlX z5tH<&_8;q&c&Cn~t&TqWuh2e9w`t1JjnPW^itpi$KI~Bv?vIFozX3a_4+(Btvq+&3 z7@>Nja69&VWk|up++z9pWH8YblrYiSiuzsCSX~fi>^Zb%?Dt$OQsj|0Zv4{{hfcSM ztG(%U&e}e?D#v>cfr|;S4iWZ5Hfvu7TjzWF_ zjd@+cZ~LXWFj+J8-;A{NAJx+@#o@o5_7qcq0U3whQ;L$xnM_nPRO*0sR-8~Rz$Gj| z%vSa?up?=26$rIsmKB!cUcEJcroYj=0{|b=CEsAWPX5?z1F-Mf zlf!bTnHgl?Tr}7n#H*{@{sLETy?dptWGf+4Z%rTRSJy8Erh9hKNJDqNRNJ_0-e}5U zGhfzxa_M0|UD{xqw`|!>HsY>Q`^Wk7U(Un-ao+yJA)oF~+GEvdO&;l|8}2{$VMz_P zm`hm*BFpEmbcWC=U9O)rm?B=T70=)^TeWbJ8mVW|Xl)9K4jO)W(rA5B$APX={H4FQ zjJhg$8bxJnpwB6XZgWy+r&Vi5^ADxCa1K8u-lGLztx~IEt9${d74nxoU7%aJT$+}e znBD|$R5a91{|EIYNn6Pg0Yba!f{I&;KB6IxR(i&*vWk9Ja;rKa_UsdBEd4+ejm%If#{iTHjs+lT}k?_nLKMd)L2w zYW#!Qx@%_ozeWrV{zu<$RDFYNCMnv@Wn+VeoQsXJiwgs_W`5StX#ce;_z(HgWe04v zslSMjY!v|HhK1`yax7y{j`NC!I(bN#R*v)7!4Z!E%Ke=(0e%tyyd|`b)iKXmnJ47N zO~UODU82QWm?vb0I%iAYmOu)Aej~wnvwB!ZirT7WStTHy&m0XzWqh0**Mc zxDQAUca@M-)aH`Oq4pm=3d(_(%3wTQ`@oDGPIh1xSSk2~*fNZY@a2v6XotmchT{T>% zD~~A~HpzxHg7qt1ip+nAhW{c;{|~YL50PFaR=p9E0wiv8%E(M76B4u5v@xK$aS1eC z$UjIzb+&TO*RW!{)X~MB#m$W64xhu5jqpqVaKzzXGZ#XU2mtS7FXsvW+s)!o{ScEf zyQMi>KjqP|5%MKpwa{Q)TlKiHOMRnt+pulHbtPZAHqEY4ORi>Q{9nrMe<_GL%Xukl znuqPT=FmFgB_>t=3umr8@>Lv{8BJ<6*30jFIP9YGT`LxgQzdIN>;w(uYEJ&O7k9+N&b3b?&E``;vt{+oo`b0b9Z#3+YjXNSjEjU#i{A05qv zor%;GyN3C=F*1xOJxxm|x%rEyNzI0BVOr$|#fEi}*FS}G7DS`v&=N6aWNrBpZA?xN zGrvI3;Di!8qj~Q`#64J2J1?)J*IeHR8v*E?NV81KmGIb8K2t6bbn|#{1~V3bgqAzW z!zHW$a`#O4L+f)#2D){u^`dB=Y+#^!{g(v@Rw%(jfOp4|HG3bsd6QFH%iDj$wf=>l zC8nZZy}X;2uGCwS=YNc=LC&&0nO3b?K5v*V;ID4Ale=rhit1()nJ2VlOKdlHJLHLd z7|&Wf;Z$J@k>d9XoU`@rvRRsTEm&k)+vT}xWgJ=ypu4k$klZq@48Z*=7c=SK;VW zmB&>uFow!IMZa9oA_u<6tG>SCPq|*g{Fkby`{C7p&W;Dyfj3|MV)-SGxDrf+S!%UW z*>okoWy3vaiBur9y-gDT#|$dt@aiPKrRKM;W+UhVfCs*G`E>nXqb&oU0&9(E)+pMp zd}IsZ@kaiO6Tp5sMihEcSDfMdvj~ezYpziWzH{Tc&K3!m3B*JYp!nC8tCn@u1@^Mq zcozROhxR;H)G(XrG=cNyRPFFmeB$}S&gw?v3tNyI$Yq7?gKYws(_#kqQL<;vg1xGm zKAhmw{_0mL)BoTP zz!=3&^7_U>SXw-H!OPOC=rP52F;-QpOslqR5H_4kZP_2Icw?Ot{)=}&HWm&XY_w{b z1G9s~Wsu`+8nCoNnKe>_WalB@USpRlt&rOv;hs8+5=tFx8)+L&ugRxejovI-@Y5** zeWrl@Ekda0hSRNP>vG=Z16q4xeRXtQC-1~X&L$;0fq*k5k1>;BiW4J7 zhZ85=g^$&b-mqllpEy_flG#q`Q>mOijp-zn@UrNgZxNAB zjX;g3%U`T-$4+yu)dOSmBiV-YWcD`~{$2VqhMLRF8`cZNRqIGDskJJr=xM*#YyDl% zQsAPZ$7=#Tp-ixnme z^6W8ZDAI(ti03f^W- z1-s5|wQ;HL$ipyUpTNs63UM!#bB;9TqmePDb`U1Yye`#*aVFTvl{&4bZ zJWY0QEgWQqy~H4sLM-ODf;6RH{D;JMlfPa$wy5Z1>7=at;f0k=G~5c#`vQ|pE#pJ} zX7tfo?~%lMr?1T_`td>@?q1#Qb`tbyRxpcLT0&+5 za$8d;*0gu04uBc@nZdQY?=*hUsdtoBe9C87T^IHTEa@DkpO63`l)as%^m+heN;Hns^`3IU}n0$@Sgqv?9s{PQT=A7(IQXz zH<@LX+Vy!C7YiH&tTv-m3!rzxlBvOQ>}u&+O1vpN*N}9NARCzD1ExzbUy5NIf|$Kw zA}j0NoADf`R*u>hiWtiC-+Nghu^Li~ld8IdwIDp@S7nldo|U3YZn1`tJ$ru9rGM_+ zFj7P~Q)k)lFkBF(iy?4$DlbDJRAt}(J|Hd?JkZHRD~;F1YQJ(KI~y}Yj*6g_ntvqG z^I?srkNwPEo0E^bfO6*#2zx1--0)MAOjrt zW5g@_J)C+-apus$)%&eRS|m*^QK?|JJy$+ZP+Q9e@^adGH3A=Q&w9JM#S8WwfSge? ziRS_7KvsB*+;mPnJn-rvnkhOs>ZXwTRd}U(99RMM6uP(L8-*=bB`Kgl)fmna50tj^Sb zOum^1!OsN(_GK<9@TblKxVl1Zr@d?%j<OD&CAEud&_s#Zg!U+ zUU?xhSY3Zn?}#You+rxTJ;?arAprGR$(SYIyBU<9Zi)9dCz2;mAx|!sdwzyrZ6B#5 z5|Va7H)fe|LEb~ujGbZNPz&neJS*nEB_H@Bv9hA=kTVb*#)8 z-`dLY0})DLo{T@^glzq^0V7fVUJywh~28SUK zvq4TgY}?-boAupNgT|9mmjifJ$i8M+sRIkBv7yC#5ZS}1mZgJzd)CaXH|J z^S53%5SLF8Vk#%q>8Vre8#LDT>iu=oThe4Q$;O8Z!LzAcKZZ))UJV@&Ih$g?!)8I> zA(`=k3`FK$C5ON1fX|Rlp=qHdlwZ~d^orxGeEi>bJ+CK621@(~IZk=8tO^2fTyTyy9 zq6(3$8m{&ie6aT&5g2a@*Cy+2r!mf%gaD$p4*^z1 zlUlfIq*^UeLt5jAwYdbZdOL3k_{9y9VWCAcr-cm*NH`kTGvi+xZ2~keW{8af2Mk?Y*gEo|muy|33p1h#VqNa{ z>h+KsSPgKJToudTG5+ zuP73JWyqS&GnlbP_`E*)$j%1jKZF9s(ox$ z3xF2)r1Z{RDuo2{!gzgzQLu}y8B~kCBnz016Uv0CfY@lzDY~;S+GS+#qI>GgSd+~T zHHwk)jG=iY%ooxg)`PWk<0_cCkN;Y_el&z>{dND%IA8+%Ac&d>LJ z!+R-o|5k&#w7?la?=}7-G&crTzFyhSZunrN5ScJ)k|sK8?mE3b9b}b zI%2&ST z4X9!ok6~jWve!{0VZT)rc_*kNaniV4 z`XNw7$oPzx$h1ra!uZCjG54d0gB0SF#Vo2M4VT1BEN|0*c|>_yS`Y9wU=@JUAi(^Q zZvSY?$t)brR4SfB*gWb+ajOmDsiDu&ECZ1V>Rz4t$VMFrV;?TXD$^`O6|3rg#pQV_ zO{r)!&iav^L|h5c*w;~l z8#oJ>Gw=8@=VmEriFm$ZnrphEu0Mk@tJHGXZJ=&z6=GamO^k2WVsl~TN1 zpf65eBuNjceJNR6-n|PS@OJ<4PsjI1N3o3*`_m`EY%eS~DwW8a$Lk2QG^rpwKT>?y znkK8zug<*!Y@S8Q%b-R(HjhASsp3X zpm8%rJ$Z67bYlTllpbQ`^inBT7ac?@(b87MYw&$m8ABu^+v7IBR3Gx;&@J)cY_f`- zdIkpF;0e+vOA{53%}AYVLu`lWBg^?m5QERkReZ_fT?6UItLZob$mBt)f9b~dI^$sD zC?q)0qTGHAgS(`!bG+F5JK~MWPq^C(V<~#zA55+#LGk(T5b05*Kzb-pPhVnuqHh(h z^Kc_xSxro{zNPjpa=Hzr1pIvI{jB^LY4>gSeFuCXz_K&0uo@B)q6%7LqNeJzmQqcf zL4o1F+418X_^LP0?ndZ&xc%Dyt1M|V@0K~vCmc=PjEI7@Dr&7_a2%r7#emP5`R@ad zIO~AQJ6EweZ^#2*Q;T7nArLLZKpY7Mklb_S=Bp9+mb{`ow6x3wurvqjAAtt0>Dn&@ zpT$MmkV9?JWfiN3@&lK6tzT205~5`UV+7#(6#wDjW%n?L^x2Ml%iiR&v}g2&W=nmsP*enA!M;kXoGLS_3b9<=Ko6~1M(TyrrhLUw9mBx>6RVXR$QAms z8X+1#IOI915HUWHNbQ!6Z7aP}dxY~N4<3?jIX}D(v2?(QaTyPrg>8xUg1jfVhG7e%nh&d){n7wL-<9fbA2N%FEJr`FXNv*#;O(@6K8 ze^7KxoH+AB*N<_@Lr9x1oy-qePgWW%d-N$=vC!9k^}ONYt|jrF#F^eIo< zbLq>fsNV}AFY~%o$_KjdE#2HK!PvdZaotdy!biSMi#UY^0zc49KON>AyQw|=Cs)>hMCSUJYFsL%vtJ;Sj9Jp9M#22Gt}{o*f_FR<<>iS@ z^2#F?qr8rLjGh7!_I|W^cw5?cA zb36e{Loj^A%ZnSaOT2`-*7&`?X37`48PgmYr}GVKFR=)VyD6-~9O|qSo55%oY0+Q66pU zLe&GMr&%^I#oz1(#o^Y^YI9Qs*E<+x$9N;ba#79%G2Crp{V=BRM?8g34dpkJS7Law z=(0}>I{7yKWMYf^pX94Fzlv0!t2%S{Yl-Mc&%6N*YPM7_RdYV7Oek}YZ$Yb^TG5zf zw;%}0&3~newyMI3?;>O^z)N+tmoB{umMJe?oTAFYM5sR<|D;OSYD}-X1|FHY_6=)I z@+C4@QOL9wjeX%p(*k0%H?;=dA7IlBdUkmQaNAuPMM^TY>%37vgh=R$Ym?j!0}?Xy za4C+OBKXz_La9QZ=@u89rVMWa0>!s3S>(C=xUk<|)aAW`Y<~4b(l`*s|CErS*njV3 z$jYL|5Ooh;1<_G$LpMXsPb5#DF%Gjno_3znmSQTEfh+`SN^vSp82W{wNSxTgP%Plf zW*3;d4K8uN&Y^DTybm5@8D0dGvN1+uvP^fH59dX}k{^3ECVND+RVdg&dBW5>!qA}b zB+PZ-C~Uy)I-_MgkLV*43$fXAqMFq=`s=4ZR1LQ;C{*znz!Cv1_D#XCC-`4MT}1@f z;_P^v*;V6f4=$PXbg|rg*0HGU-H<*)imTQ}G;UIVhhAi2vOzKwX0iHDZ zR`C%%*$m;=t6TyfL(_Wl|E?9=Wl?XC_OM3CE9(v-nMAn}$)g9UO9c!}Rtb7I%SE-l zZ@}Yr0Efe1DyzE+wi(^|f+rS^Zk|C9j0v37nz*1KN&lO^-tEU=UdH(40yb>X<~ZXH z#ZKURvRmuP(~~st9iFv!>L+JSasPNsGG68fGTYp!6(s{2Kcd^2>$2Kbcjepii6 z35f#8VFC!Io@>_}Jeu$=IGa+oE>}Ddh?;;c;6Apxw=UGX$X=OjieuB_wxgnS?S=s+ zcG4cqz4Qnyk+3U0dxOw>5y+Ke;mKaxGj2A|%(|>R8f=B0481h!-b{PIz%=dy8a$SOyCZp;1Q3^dxaVKZunHRIpyM$y%5te{TX^~VeO;q<{i+9i(j=WA zrwG`GXk;!1V?lu+q4zgRptS1y@Z$LD?uhVzJJlRBiiwrZ;Mca6UE$aV#eW2jJX1faFJ`$wFK0S81xR z&}GC@2hVH%k;H!U?khud;2@CK6o~Y~Y@7<`+2=rAetlcbr{MM^3?W6P9+bsf{(k_V zKwrOP#qb_4OUMc#>No7Vq6-y?Q%z&gupM(iV99Q@Yb~>3*Ltm1t2V6H+jgtl>krxt zQf(8NC7?-A6k(&%J&te);Y%t638Vw|Z$6N!#b1S8$NKSYuWdI;r#o!ahK>5L)@n8D zY}@P9yWQrXUw8WaXta(FgBXa@ucFWJHuiEsuiF-Bw+3wsj*MN<@PjV=%OqyaB<*gi z(`eh4)m&SzfBpC#bcz3k<2d7;hL`fY1%2-H>~^OEC)|F!OJIF!4r{$Zr&H@T>q8rM zqs{hku(m!6>t|?WP;_0CQTIF!rlhP6LezaGq2~~UsaiaF=!Zq?Dm!|Ix4ETYV03|W z0}qBZp3Lskx4Wa|5vY7Mw!S9eV&Bt+Qn~|7JPQqtsVEia?zQK7xPtK>M-SrtJUKgZ z9MG4+qK<}ziuN?D7uh3={YdN; z+tK%R1?M{>c~V#M?0V9t;PNdFr3?D<^Afpz@gC$zyL75&{aTY5lw!iJz+U==#6rE zn|w~IN)s;c^khcQya5VZALzz?f_T<+YTaLrh<81^sU-WiXS_1%(!3?#qv48?_7~Va z#BNolL;KnO$b^q3x*=We3_NQB+a44KY>F2QXDA|RW`LVC9oUXFMGO1`8v@XtOHjmI zL=iKCBCI26sz{Vcbx5L(f<9q!1%>!WQ|7<031u*w)|Om$nZZU(Y`BwIU+K1;Lrd6` zV>bNOiWM?%*u!pdYbZ;I0B!XaqWsasv5ux!A z032llp>LD-C7!L@aYq)am=7s+hWsPt*9 zgS=`rqhtJaY{M%aIvTZ;>Lq<;BwH!i#bQ&Q-9=h5ql;Qp811f;t0rYLgg0-Hg zEnBc)Q3=NTIe74#k1P1k*Y`8n-zFE2kHjYKEyY-1LGe>vIGTk-v&Nr|c+JdHO4zdx zuW10#IUFIQ-X4HStI^u8hySP=jm$15TL_1u)k-*)WHZqqmF;A&qSDZ^yS$(q37R=D zuXtt&<9)_L!qT^CLx6NvqG}}!c8-T0dRCWuDFq3ScQWxJ*2nk;L_i3nkH}<5jXpK# zY)+9Dr9`N7d3e%_r5uz&`Km%cL=7xCjbsubWbguwB?u?C7I9fdb5dNRXqSaAWb*U1 z9FMhZCjMg7_v>(gh5rJ#(r0^0{$ai4Mal(!$rEI`z%N|jmpGWxfINUX!X*+z=BO9? z^@KFcU)IyuN8PYMsck{b+{>!O9wnMfyjy0sv0$aj%o{14VDZks3ecH|)|Q+D+vuGiFBVH(~(j6exfl(#l zg?)-kMezl%@WK+09xd;ZUw8rvt<-*-UBhH3^#-Fz!$vRebMe%x*-A^_ z_y_bP2;2;`F4hF}&$WAP(I1&^Hhf7xjn?k4E2&PZ{b%$u6pqCt4HM+H?MXgv=)&=xReMgs6Fv44u`H)gj=v1u2 z=b}pp^#v6T_+He~2I5QDT8&^eXfO;5p>~Lt&d?`jE7b+oUIBz9#tfG!kS;em$!G^-)ccVGhv1>U)2 zrwLAb{F?C7vvK!u)X~ZUe4!5hGq0J(`XfL&7 zXROs9_(GlZc}L6Uhr~L)AeeYnK3JxXWa>JIb&1te-Kb7flaH^lp5`SQq#y?a`It7z ze1rwhdrua=IlyZdQTm@p)dk`0q?7?Nd5^i&66@9y8#)xmB{C_J&-|PyYi}_C>4K(K z1PSaiA6ZBR8Do@feu@s4+oFra9%>T+IT8~d9 zCujIewXKZOUoPBC;%)SFJ<*lqRx|xF*>LpPj7_Fu%NW&JxY<;0PZe9sSk}%=z5OD+ zLOf($j!&9ivda86D2(0R2#H7!y|gM=(|JJ3huJ4S@u)g8e*#<5Fsx4ArtJcXA8|6O zVis+Iy41?tm=e|m45$>=xZke0u+pk&Vcim`O1QkBIK{-2Qnd-^lJe5Dv6XT~p>j-| z7RwVmS$MuzqhU^rG@eX@_X{D>(xnp%NS+&B!_A+vb7Y8LJyZOoufKHfb8cEvb?*9B zzTtX6Ef>1U;sc->%UX&dEkMNoPVS?$s9U5r08XclA4R*s9>^Wbv>Y=MsM@9+xlGFw7MoAmDDv;qQS0Wa zYTaB_t((t8t*79JY$vF-m79S_0CpFZR2zFsS_;BG9D}EI>JDg5oEL5+cqrg`hC@iW zeOhZzT$`1Ug!Ze5!RyK;>clLUga#?94^44$W??nc>?&dBf*97vQ`| z)3TDG>tVpONSzutC{6pu$;`MRqX~7?jD63ALxRB6m?qy)TB8A8~A*~+`)Pn*q#Q4pI+QpYD(}b7hu-{;KT@YcX2wYNWIyZ0m9Nze@rIflmZ#l7n5v~Fma%larS_0|IjGpu4Cq$vDHbMO?P zfh?8Xoc;N%dYrH^({f5e;{su4c53GYmKf0gOtI|fHVUp0dapXW-3znc?Wu_c@62XX z_giew`N5iiKl8fqjKZQka`GxKXn;1tyr*J$Q>cI}rl7UM-2oL0+DcYw#iVA@MWPLI zozyX9>+$X6BTy}_QwtD`C&=0m%}&nLRD>vRDr z9TJISsaeQ(pf!YoK7~~eRMPnLZ(LPh&q*tq|rT7N3F|>kZXt6A4}MTnP;D0z0Eaf=54X(H*)OxjnbaKZhQVY zAl-;_&QAQn%eLhw*-yC4UV<&Zk*U>_!+!y`d=B0M(>N20D9ed3EK;+)!c+-1e$Rjb z+Ft?|iwn$P*HHE3%-}I0D2@!;0^u34bGG;l;Hm(9Rm9K^t%@|&Z|H|u+ow|7Lb6vj zw}Y*ksg2=%NEc?{4ykl#;B^SK!8#soEws*KhRI~On!PGo#l_oX(s2xG!Hu%9{FG6s z4L~tR_Ft7bne`V9zqD5v?GlX?i`6v9i~68loYw7!iF9-4O4sN zxQL+y!#RHbP$ zkr;C<-~l-T;9{m+FtBR$;ZQW9V*EjDN>hG}@D4T09vXmmFGkEdPAN;5D%#a+L(y5O ztRfbo=NgMZ7V!=TcESLEp( zh8qj~DS%Qpgs@bh3P5A&6$XAo0qS^QXh*@Yz%_$$YQ~cp#?dFY7T^FJy4s=4VD!8D zi3RJO58s^P1q*_fcP%#xhPM#7Tq7pgVc!Kr7ek#eXUQ5IDeP0?0+sQvG>#1QrLZ?| zZ!vKNm|Wup<7sc$JG{y`@+~mE|EGbeFIcxPcZ_t792)fB_5RbSTmrTd+%7an>&~&w ze^8S?4J~-;jezL*6zR+HH|)1%V9R2}URalC-5?Fo){1-YGSlW}5v#Rlt1oP1h1EtT=tq=~p znmxoMT^X>LJA*JyJNrtMv3d7zV8KX4XcawyciayU^*;GW+2$quSw4p;^KUSpa`cGG*xnSy|@Mywo92Sckmv#{<_BTLfwW2)8+0 zU9i}~&Kz39{T5Rxyk4YIpaWBtp4=CRh@z>H$@&A<=D zap&XmZeV#gu)y8GV|I#qdu!37={he5wmXeydjXIsjRIHL-pxl8-;;fZt-7{riS*~X zt|P9mTcI{A(6^}03%oBWR;2Q#Wa;!z+fu=f_|Q)O=88^##Sm6@`WG{^ZpP`aY3An5 zmQH_}8l}^}boy_-(_dFfE$#GQ)qk;s+dpeeVH3^bZvUcfu0ca@i`&1In`Vn0m`ME{HXzb~SfA2BQni0(jBDzy@~qz!DzD7?9<7Iv1ojL{Ps!NFEKxjUe{h4B04LP2N<%e*?Rejy z-5(<}0AK-o%h3sVNQF@D=;GBWlQ&WYy)cAd9T9I<8|~t11?TXK&qe zx4z{pcMaE^#1$uU=_Q>{=<}Mo^-W)~-3Z?=Bqe<*z_9ssW4JVC_LE)Aj zR1t_=TG_>M-3e@AtWTFae*=&&!xyF-knzIuLdbmVY>XA@hl%5&`W~`)7i31m{ z=_Lymq9NbjJW(c0@5`Pb8)obfOGebAVu>&>!^&mjasZR^&YlCBnQ!m~TD8TvthL0AaogSZxX1dDck7CR)XD=SACGgWld2+<7|( zcit{>=bOWw<0*)q#5oS)#G|gkmklpZawM-PGve05%iEdDKyvuo3@;y!EWn!~%-gqF zst=efL~6%D<`%OWU`a%j!=WUJIp=6`FNqTO!HJC7%U6yqCtn|7;1m2Lmu5hj8|M^{ zj*h({l5s6SZ>l5UYhXO#-8nQWr;{I;19I;V^f0D@nnf|jO7O<%M1IBbRTSS)31L$_ zq0IQUM!rsv9K0BNEZnkZzsOEgUL=HRg@LP}n6mYZobz&on6qD^;4qPgRG3UIy>H-u zPmnX5<}^WbQ8W6uI1S1!j@~5D*_wop$KcXydhaRB)~fg%Wz?+n#D_wZ60AxBbE+6l zA($sZ)5Jg*fIVYTl)|3M;-1~p5K*bA zrQR}TV7+31rsPCqP*@L`tU*H2nUFJ6oslS-Nxp1yEkeIYT@u5-^b~kV(G3b^`_qWP zrY^=rhsK|#xV77lPq&u~pe=;zxr!KIDXdSllBs}UqIF$>m?)nK6iYOy2N+AWN&!$$ z5j6Ic0b?axicBwVEHxc~h}YNnp2JvSmt0pG)oDCHYBRgF+!1cMmF*z_%>@hCu{A40 z%@qkX2Y9@|4M2u$vR1H^+1v`Slu(6F4KA>F&3HQ(A?Vs zqw3_qs5&K#YW*;(4`9c=0#hxRYwg+9t!rJq{d#&en_6A~z{T0s6ei+jW2usC$t%j- zxV2cSPUdQm9R5}(@U*-rhV-d7!uT1Xgcv9x7)8^j+1!9XDSB$?Pw%X$W6(`wun*o; z#Akt}b>w8^^~8b$|7)ThFsT65XY{Qxocd#9JRi;634AjK;93!eg=zp6TBZgJPy;aF zQ{y5WWai1EDDcXUfvRXcG-bfdEU49CDn(x!3dA$?{X*_);RtX5EL$I@;Y&P@P(R?g z7~LDk_aGjwok#}fu%~k7O>};Tp@E_ZCyESyW9tjK8dwOzvA;y{o79)~IqD|;f@S9v zajIh2A{+&Gui92X!X7CS5T*zckD6D_sH&u8a+tL5VGEZ#zj|Lh|1Jz^l)_fMrGF&| zrKGbw&*@t9yJFT~gLpb`{K@@=&*ocj^#?E$z&e7DSqxk^@O+v|_r9WNGfIXUk(sx2 z;bXYIj4H7j0$)Ji;OOA_6nubJ)@W4u+wSG%r@vNy_+bcte%SkK<*#RdIXUAQ>t9ta zFP%SMUhb#=+WU`B|LgMd^XEVB!GHf+`BeMiGgdu=nR!Lu@BI~wHG+ztE_cF489}h% z4b_{Le=L zi~@6Yi^tIM2>_=3j6kMOKy&KPR;gGwsaPjVC{_tVb#x7lED7KZ>+d}SeO)ZqZaB23 zpys8JdIKMh!MNFl9$$J4t?35C`R~8qAI_$8qT2gb(`y9c=+wdTqH|oz8&sXM+E%U~1oXNuFtpF%W)X2m{m57wMb(flU7g~2iD>E21 z!Qxp_8gF;^03EWxp1oV;wRe|Bq$F+lV84E7L`r8xM2cbvDfEPg(~ zK*rGE*bNAMY|X%K^#v{G_LQIixF|Rjib6e84!se?!6z)X56{hI*w88b`wO{;p*SHl z1)~RHOrO9vEZWA%9lJBb8n|$}y2l5AXYr(tI6Q$YZ5=vwzk3Tda6)2b_KnwM*fhM!jxXzB(G3RgfYbn*vRPoNmZy0*v#8Ih-t%I`+>f0WbSK1m`OI*I4wB{S0&MKD2RH6Nkn0@w>zy^TM(FNZIXUj4uu75Nn>uVVGU1_*L&@WQ}be`=)PozLOePUH|O>`RiDishgZQ7cY>tpe6ss?7g6 z_r4f!QTj3putqo}Mfmi&Co81X0w@gi1d5V$E&QI2fojkO=EC9&`l7Cv!uUka z(-`0&44=d%ejkM=;aU_$P^x|q{>hB`pJMglzvHW5W=&^+Ci?X0^X|g7Kl8Tt(ygl? z6nwZaAa^*dNN6e5WPn3e^|+d>n<6h%Og*bEn2tnPEfn};0f(O7MJaw;K4=Q`t=VS`|?G$oQnu^sN^UL_+V_!`5RsC(n zkZ_e@-4M?G4d(MzN4v`ddTn~WTLx?*BVyHyq@-!r^MTk3yY|+#mgl9u5Nk@gs!4px zKDD2rZ({(K^s<$1N2PmXjVxFMGkc{{(Mzn{@*L_c!Nvow-IM&ad$MTlo~*WZPZqD; zlWknP^;Ydg>$Ubuy+v#ElURzAu(;*Y$z~^$RiWc#(hG20J|RU<^U}sffV%j1qS>sj zGHuah<-qC+;O5L@E!bQ}u@W`M=upP7TSFW>HHPgNOnhDd0&fND2F}&{y=>rIw^st^ z)*9vMi3YkWquMP;x5T1V!sm+ls^O4lIbayyF8Ey19SN?uhlbB}SA@?ghOjbxu9&BC zGw?Z0Pcd({gwM&;DB*J@d~WOEbGk}uBk;M!zVRjCa#^DZo9GsY%N1>N4Z3+-;Bvhj zxLmJ<%WVKI#{NH~n%5Qa^fq1cEl zc9!8-_W}Zk2RDXPgArh;B6&FjM}`L<19uZt3#S$iEnwB~$Y|C0OQLsFs5yR*W{?Hg z9CV+Dds-P;2G;I#FuxD2w=I(s6Q&*MceN#(p6HMa2FM~fi2-d${JwU zM6b99sc4&PP}keyLF(stkou(uY5g7~_Tvh}o4jLr3h<)zi*_L;`H)wX*>7uINc~Km zpB(;HXZZ*{M3u3R7vXL!&+t96$Ko99iUb`Pp8;O0(|y1{(YF$yWfndMdo0@FeIu3t zWWZ8?@E{H~B=;2Kj5>0i;t4^Lu^HBu97KEo4Wj|Ob!TwJ!8&!0<2{c0zAir%3l$MD z1G6sEr$b?51?#ejX5jD*hIsJt>QmQwKzCF;%<(J3y7ntlclcd%ojad2>in-Bk86P! z+$`rG^MHBnhA{p99Xd2=8k&KHlMT!QR=znLifJfKn0&mpD4J3w3&30f82kPM<1rS| zA#tlzBo2@sB}Sngv^%@)~`RWFtVpYu#Bv~Qr3yN~OpUB{@=xg$=%*&ks3jKhui??OjadwS_L8nXqGf)&6i>mCx>0jXbmK72Yv~TZGEDBdWdoH~ zt8iIWFd!cl?r~sM^M@7IpS@=eGbpxlFAh@lyw>k{SfYnrptG{5l`C+$0+%cBV`YFV zSKx95-V%J6{&s0y7&7(7Jc_`U+K5F^GrA#^w|)edHuvV$mw&Ka}ww(#}zE3_diVufHOoPcej*0e!{%rkeruY5J{s zvn8NUrbY?qE8{gz#j$-w+gyX@-WF`1S0`;)NOYc^?j@WbEIPngyey zS%NT~)L(JB4@K?TWK}F7|8YjgXIzNcSO;K`djAoLnFrfL4+P;wPABidDxU}SGtzzs zIl^>S5CX}m{*ls_x!=GKfCEdpT?ipBQpD_bz0Sgh%QHjiUI>Eu5!4#s7C^Nchldg@;|TMFB|}Bvo|G1Kz5m1s z;)Uq^*!)N4{Lnga7RrKNoq7O;;Nk5hx-FYpAiIe*XBSB37XYjxK-uDBtK}RchP^)3|IRxV~M4l#>~#20f$g7UB(;_!*fRC4Q#F&ul$@Mqg2F z070YSEn5O6lR2cYjpWF5fHK8fU5lFD8nBF+2bM8Qu*}9nVf_lI6L82oS8u<4hU^%nX^6*>HOiGiJ8dPZD6OgJO|T9*m)j*;8N|SOBa!7a2seaFGIY?k)TsfGOC% zF~Graegh@Xd>8D))Qs(sY-eVtn6k;sUJi9DZZ)^`&fw~TA}lE(sJ0s4k@7lA(jumMO8wnvkkSRRAiV8LCe zEC?&I!XSo+1?+2CRVr zaX`ADAxzb?Z@|nRFu)88s0OnzLVf{`y7%Y6W;lH_4i^DRK!X^BMcjAag=cU~-vu_p zzmSpeZ(t?-3z%t_`dqL!b>hufu$P{QA_k5h{)h$^+gPNLLmFy?e=XA&3Z7O$&v=n@ zsyvatAweU=5hqL1Zz_9m!Kr3}4A>+JWEH(eXXwta-WSim^P;EVu%aPJ-g*IA9HiE0 z+9cLW^0IVn)KyL(bo<0ZsZ^!rs@eXczmt2F;|QM|T~l=4xjTaeeLAXg3VkH77Jx30 z9wE_C&_q}N0V>KseSA0~!x;kLpQajQ#!CbcZ3pwB~%t zd1uum$6t~lattB`wJ?JXAO#i$GzyEH!9sTq16M>YQV~5BCkLbBu$VYxlqD{6Q`w;$K&aS#7ICY;4Qv;2b6(@DT)HeY?+bVpo#KRtr5k};%%^o zqbA<@(=q7ps38%KZc47n*N{ba|Mnl3@Mj;j?-lIcm8~AT!{G&>Nn#y@XJNM}Vqy0H zWCV7%*63iD(kDHve>u$PCkA($1I6fqrvNU*#h9}?t~+sfg2eKx1riHYRuKD`q{+e$ z37r`)8EMj}M@epo*H|Rtf~Dd$63i|N@ib+a-wOHq4Oqmb20(V@r2yUkYgF};eosz* zHFN5-nCvm}9*+^1RCCX9nA&s16k>ew3Bw9v$V8Cy3PvZ|KluFTy+0wm$dnL9uzL*1 z6`oHQN9f$!+fOhYSD@@eiPJ#-z6c|xWo(H;{c^ZmHyID6T3xho6sCf_#$--Nl4+Nx z_0!ulq5!Qyd%%G*RIf{tDy+Y^ft{v?TyJ>enH2ozBK*Y_z=MkGliY}}Q(2VT`Eom7 zZs#?(^T~~TkuChf)91r%=GU_jZ_PGdz_ts9JX8lZRA9PQu@Q^JKMbcbTs~yttG03S zb}eEA9GJ9uL#r*T8uls;z81*qwM1Bag2zWm(&Phu4p4plQ1o7zJh+VAxNaOeNGLd= zog>eA!!p!l0K?y7E@*4?0sJ*+$z3ZP-K=M48*X>&B^*7P^%9Ow`J#lQKffrBkB!?G zdx8Rl!qS@|(yt%iKTRryTb%-AqIEYzC+tpnyW#O}HzWaK2#tF1+YRQbw;NQWSp9ZG zLHO8)Za3&c_;|zR?S`1LDbsy_kzuJLVokk7oGIef<^lW{?D7ZKR9tHT71x?Y#q|J= z1ON#nceOhJdez*ZrYfaMot}3aP5voeUHhiELm=?@=ltS;>Yl-iz1^U#s5WrBVHF_b zGS?o=+zSfk64xFIx4IVny*1Y!8hO_q8s)Wz^_5gat? zqV$USR+0sj`7S?%TH~WXB1cgC+GC+F^)h0LM;BAe3no63r!X=Z65aEK^DjbK@BVUO z9Q}In^XbQvi~ot(*7N^)e)0B$QHi8g#6|2PLt!(>jw zmBSxO)WSbc9`0F+Tq$yoCUTRfCUQ?EJ7ogS$`Bd}9VnOu*&=%e?pR)Me%q*jdu0=y=Y}0ode)`Ey?RxA{9C;HK+qGG*lQspF!I zmbW$0@}wM;4;3lTG+QM!A)4wEnn3xYgeIh*3AoNYd3b_CYvlPS%4m6o{mDrK9GadU z+@IW7)t{^y#p?d#g66pm`IB``q`cwMpDa_O^e318)($lP}{>ZsdA? z8%y|;3%9x!{k=8*KVYrL(3d?WHpWa4A?&JjogI!W@LZw4kvq)ESDO$TWY5B^mDYSD z2Z)33G}&QF?BZ0;j%y@Lic;Q@$m|bicR7E=sN3xuNYUH?;gF-!ox|yt5-AuI9jRqU0wa+@3QCA(6T=(ph=LBAdYIA|Y6zC479J!)n zaVk>VRoL9xb9%rGx2tRq>qM{+#;*98*Id)mlPY+ZVKnK@PdcQR>j5o{O4f2q<98O? zIWUk(MXAWOJ}gRWe|nPCgvWwhs6+-qMV{kYsA2YugYe%GZe;{#tkg*+Ixbv#Dq6=y zIr@Amtg9ke2(YHirq#uar-})ZT(7g&Zv|skqwYfMTnr?FrQ)QpGI(^95|&!{D_R5T zPYytZe?L;Bn{^|BTKQ0*4{(y4k4rSr8y}?G23S%i7$xftxGicjbM_IMzo=#)$L^HA z$n?GMU^+IGm}iCtdixd3BY=HeKia2R1{;y4t3>orz9u5umCtdd)Z@9!f#dJ*>L!*qLvGtfIZAG;K%+reg&}Cpx z&0K&|a|zf};a1n8!nX$Y)XIZBwMy92L&Bayo8SvaKeaMsPiDvMML)H&SB4}3wjcfE z4rwMgZ$2J?^&5QKj&IMA<3w^!Slhr?ARtZfiz1oOUtp6;um{%fz_-D?4sWQ`Sl|E% z*S9%a-rsXmm;1St0r=ws2-|pdnD*R(5CP;7Tz4s_Zfa(W3CK&Bh_6fN>JT{KskAVfspTQG$9__Qv{ww8iE zD!78-AS0!iKt4u|F&tzGNyNNcn2&B}K}@p?vF8F?P+jS{Xw+VyshZY{S=qZa-t zA&DEzCB($K00ipYd^FlPBr#II1$y>Cy$bbu5!_f8i@?XPSg60f|d^ z_rZl`Ax2)ui$`5}3%z)uKk>1XdL{j}=&5{gKw@iEK%#0Cs{;}XdUH1fNYwSH^M*@6 zqD+kvkXQl|w;zzGt*AB%NL&}{VHu2KD;G`JS^}e3xYf02^{v4uw(~HG?GmH7evD#( z0y&`=MT{JEHN#9dLo&Ux2L3Jl6!xJP4peMs2%yZ9+Y3}|XRjDZ0&FW#kqHgT%y}p= z3{!u>B?h2`C^7&jwBZ}T<`S+BeprUtu7N{t4&p%pY!iClegD!s8xcfapg)l16k}Et z@T*aK2Mv*6O{dm9fFuJf1`fXoCZoa}9)XXp1N;{AD#CX#j2tG+v_^A6zwEKxVudJ@6495m9N5pJR+xtXW4uj#sD{Fg1$h_YG4D&N(_nrEIc2LkbmAP} zdt|h4M4r?Ubcw@0*@vO~V{n-7iC7$x9DBD+~d27fa6=BY=H zr|vE0MKiLHVNvZY0OXGhP`o*REB0HX0X5o|Oo-I&4yANk9veez1+Os<3jTj}-2 zzFPNN4*o+v9}eKdVo8kULO@bX^qo8O?tdOyjQS@7-ns%TPr6;VVL-#t$x~lG~Jw-g?39kdi zX4ARK^Z`Y$r~VvmNLmfkg&nMV-kXsh5J&nzmT@Ke#_M}(B#o&2>2pr`H;HMkZxP+QR~e zaAn+0(*1yj_Xo$_v{%L5s7A3m?xvt~VnetaT?Y|wxWwJa)F^Q`CGKYXaW~qEYE!tI zf)1-?kTvaG3`~0oWKH2#*P^$#23ga|L)LUkWX%I2Yla|aFu^6lW+9UbL6muOdl5FB z?6o0DfR~K0;XsctFa#4N=95_teq)sAn-YCf5Pf49y679jSPp-)9QsDC`QQM8#aY)> z;WIv6?u2DA>>F!kIdDz_E}{q4Nmm0pUCI4Nx+TMND16cq#%LjMp(w!TX~TOSJj)Q3 zJ2jez30f|mM+?mp6Qd=dJdZ4ftD|`Igh`3&S(`{LB}a?lKczIKr70il!z3#g?URl5 zSsLk6jq{G^avb<$B)Z1FxC!7-f{CM!WnzM8$Ri76$0n6b*w8{AmLYX)WF@F56~lz- zSfBcIf1+;2GVI9<&cs%Nisb1kGqF*=C_zOfsEGY3K}98~s00^x}f~Bt0(Z zYj|)_QD;?9k!lpHgNh3JV>SdT()Ee)hD%V9OpOv$RDz1OA5^5RsGc#XsFMr-=_~;% zD%|Q?^!C<(in@8AqHYN)S_`P?{I{PlBKp9?*y1DOn0WWbMj`m4Dim+e7tlG{uSnlg(m|mz;uE%Y>vU3 zu|PzCqp1%pn8AOoshs|AM64-znG6Re)w@QR5x<@U&jgm}0)x@mmKTMmnfN#>E;LUh zgLf4Q02~58N>4>q%xw6kQRQw;4A?UR1Ns+OO2NkgV}%iruOLS*;!%hZpr`$IK0?RV zz=f0EJ&t8;;r$*=rO3wRPp>V{{hf7xFsF$N2LwAmfJym+DVgrUy)-3v)@Wo*QHN-S z#Q_5n`qt7KiPE2Y)uG47F`BO>6wWX6@#*k<<^sAK z8Z;cw8F<4VeSk+YvS+^@y$}t(o&%Mrw^L+AgdGlUfGEW0(D;SSkk8r4mcNqS(}+C4 zJQs(?F3`v&&aV7c$^au#>SV(5PD*bXMd3waoaa;fWP-$}v_F(PCx2%_7On)v{%n}e zp0U4A6U4zk)qnz)07-O8>8I!P2JdBTWXa~l=do;TFh{r7JXaF)jzUA3Wq})@Y%l{! zRhN!tQQ3V(4;kH5ZM;&j2cH$H-BZ0=YFf-2It!kES4q!-`a6mgRT)Es?y8$$ABvK` zjL2~216*=ygnSY!PxHPkq$7t1g@lykZEStL@`sg$9PnqAxPQNp6RI(}j$uU>(d|{b zk8vD9#xSdkT9vpk>^=M`68ht3y~rPp_RrfM`NMn(xk*&($z%EFPuK>9^(Es*dCZ5e zE&|{jI4C$ro&$uRP5qIsub>8$aOZ%QjKDDKU2YT{D%BYy0#SF zaOr1~sZsiwNaGjc=)!=UeKPzNJn0 zmd-GcJBS#t>CA^+{R?+7^)f_F=DzK9G4--Fev$y&>SD6NLUkQER3=<3Fcpy#cQ(oX zB}|6^E*)T2=q}Ay0Ci!a@TdW97HAw?5&R~*@G1>>?()Sxq_6{HH-J8%*GPDw*r$1i zi@0%wtQ&9mU~ydX8jDIHSc~^2<)G;b2hGQM=%5jgXqM?z4W-wD*B1Vfx1JNVhZPd< z!}>6=3qKtHA-%eo=hMkB+pBsDCj6)?d-<*(>`%(vFI&46Mt=GAaWJC_{7{}gd=mGE zNCT#T9b{MWtg5zjzx$`-CEXnBq@FZm3BN~SdWHQ{5G#DQfKt);mL^jMi8TBx@e0(z z@+T~|pYHjg@h7m7-^}(^9Doi)R#Eg*st`VkN+rf16;8ZKOv-#Tin(y4o=??0_b-QO z3ddb7$)PHyM9t3&CMD*MIyqxeJZY@Wdzjv+8kSAGOtrLb?@jNx^xbTM@8-!}uskM1 z;sxaGtwpzFr3VKbCbDdoew@6R+6iS?x^l|=-^mj(o&_J=mD5|*m7^NP>aLuE=j9E# za&&DLyy4Q7BU7VYy<>DNZO|?p+qUiO*vXD<+qP}nwr$(CZQIF?PM-Juew_17ubG~! zYpPbSHLI$t?z`Z0oK%x_?RsoRU7@VaF)S@P>1OBA-jlL}gqQW-A1iDd4PKgi+JByk zm&M{?*XH_k6xD7w)9>>+;LY|0Lzwt(w4$`*nCS)3oC?OwoyVeOeo3I*_@3&Ha=}HF z!&NZ1eFGR009*K1w&7Y>RQpFEtOKp5w?G0g8p2Db$Gy3lI6Bp(MQ1bS%Y%fWwdb0% zvD%}$4zlQq$`{-+9`M>B)Z{Whr}Yyv=7-xeYwUF-<>w(n0OZdgP6g7 zo*Ue$6NH2h)NvUl7X%-mndPom=L=^VG-LZsHYL@#GHU9*ilXScJS|Q60b8wghy(nM zM2>O}+RSprKLpxp-pbT_*kwgEN*P2`OMXBrJ=Oq+41I;A<-0p3o0)5hP$D2Wp>Mjz zY~ooS?hdbyc@5N(0TmRLC%)2ey9QAY0zbYO_#@j=5*9&qX1a_CKQ9c#SIOn)Es$tt zUc=47=il{a4L~dKfU-Ic!OHmnkvI=+G^ziUPBGC&nQ-ndQx6+f0TvvWP2U%yr;i;% zuvgKF#czxXklgnNPa!N3K{|5{X>u3cvhu4?MzX79OUJ~9qIybB!`~gG`{m`8cw)#> z6fzg?XR&QdfYu!dib2o7-#p{sm@fg~h4z#=0kYD8H?&|#96j5gvxj&QYPGA>b#7F5 z)@NN%uGww$&sLCk#M}wWj5XjZCh7~)PuWFGX#x_Z$s>Xa0a!fe5IZ!5@2~$hVF$`yG|M%$5I_@2{jM8cvBiGJp8DH;REh! zw3sw2Qj-gT28N1WcjexL++@ypb)|2;2Kc(cx#{W-Et6&P?iojgVrN-)`h|o-iSmxt zTrFs}?%+9sx5hPjtEeaVUj6yo`n6_?7uHWy1pd}!SSxQCvPoNa8M1v9@bB{PjIQ&! zQjG?OhWj_2LWZ9ql_v*D6`S!1TJ?5P&nH@Sp8~yV=Ke?|&`L$nP|^7+=Kd<*`>MY4 ztiA`-$I$hhQxbHvY$)V^B{OIoH~&Jg%9Od)D(Vg=;QVN$=BrsU76)$iBd6$`x1#Q^=Q5P+wR5bdZ2i&04kudhSK{vM0slJD*mG&opho>7*8| zu2(Ok|M;9&xId8aEqfDrmD@FDu_tc>?I4?xtnxUK^2KEBdt!^S4u9F1@Yb9ohRGpd zD3{$2*HNTnM&uQwyZ}C!NJ=IBdX>n^1_doJ$jx0b5<~?JN=qeQD;8NOy27S=z~x4W zjT>O6<8)QkooqirKYK)6rP#PF95r{We<~L%9_rMIClykk^;!WFSNob(!k0EW?8Twc zY3o_qYDM5~D&-G)Rsudepj-SYr?`W?IrPYX#720n-edssjH5B@LT9feM|?=v<5iQ| z<7x%zaX*&b{;kpXKTgq0x94cIv0o|{2HE=r%IGx-!PfU)4tw~6UK*KgeK;P%aa*-1 z6v8jmVXRHmnWct2Qfl{@vZa_h!H#x>7JqGow*I7nr(w>)!dP(Tq16j{(lFC>+i)Wr z^7Ai8`;O>Q%LSF4qkx+ z&d=CW?_;ff>JDKxJo0H3*iH+SpT1250#X>+G%69AEREYP`%J|@#VM;OT>9|Kxccdwm+l8u2q%;4`Ew_YnKc+P`}-ffPZrA9===- zBNqlt!j;53-k@RRmB4o~z8(EzB}z$eDiX4XYQTvNHi`Gi==Kt44ME$h5^&pwO0+KE zXHgtf8!-ZtW3f`GH~6hvmdq$ni}lvYssxpnHh|Lj&FH4SZ?=k|Jsot~-#dKw?z_RfTya z`=JgNBo$wR(y-{XneEvi7!8A?FTh?|;tgl{h7`f+)mT5cy?mBj$atnlUWIQyV5gtn zj|l#rV@)P?g@Tf;-=VR5DKvXd(qq^(Hy(^Fl3G3&QhJ|TytHwft7v_dxrQDaw(0Xq zj1F0Iv#)*5?Mba(Pgd4$GC5j4Ow2oVJFmaDm+*i7-u!mWR{HI0AOHRxoN<+?^_%}X zHJX~4YSUoL>z(M`viV^>rNjur=qJ2L?8-f1(q|Qt-yaY$?4rAMW6=EceAesvoo$*W zQ7_67@e1A32IK&$`u(%Jvr69@P5u7uTY1A}*YKKhjQKA3?ILr4DSK6Je@&t?Jj0=h zYOLHOyuLA4B$0IZ<|Valx-Pm&-o6d%2O#cOAf!P=DwB<@K>|>K4V$;#dn1y|{-Ltw zPzskdgCi>pEBSGoySV226Ez5UW`yM_W383HS$;c@fKfe!;hPY`!d#pmyA9t*B<5m( zn1TE{uHv56ge+1OtZhO&#P<`ui52b7YtfhyhImJ*BI$>zLJekhE-I_DM(1=8$L6ww zv;F)hFn9fFR@7D-W*0~_TCuq29(?WvJG|V??$wv0(D`I9z{H<0M?Hm1wCa2RAc^gb z=~9CQTOxQcT}>S3Qh}%_O5B1OiyqZ)9c7@qC&5xH1md8HwDzv;VBAvA=1j5oA1WOO zSv$q+0VP_bu{tk0S`2DnvAS5wgvWq>Y3#TJGo$sJlF{4s!l*3bRt8)nSbZ!Cq7WP& z_nRASoyXoDbO0P}KM0{!G7aM}rub|Im&>rUR~&A@M30of6lwjx$8`dP+2)o5n5fNC-} z%b7-CjU;@BiFRQGeBOkmcnum(=D?{$sKT_=>OzRZwX~n%s|9-t0y`R0m{Y&F7Xwg< zYcK58&HL-*jhm4tU^Wq}hZ8PgdL_)%$Z@=?(r}!ucqQD_jWTB1@Hs$@loh8M1Fl|R zcT>%j)sb60*Q_QZ)$P1Qrp?sN#dJplO|T&a2Tn2*(!9u4WRrxst*sTvj7_h^qY&O(TLmx1GOhAlY`~jLettm`*};OO7nCa^2C2NP4n*0a2wfK_OYGsxEYhrs<>ov#b0u&IVG z!f4c-uPf`uf^BA*^C0W%za<-A+~wuY74xN8LKeLyS-u08nPgX6s(Q-hcxmwhQSjfdo0yK=jV zbN`ta&PC)D=m~AwxO=9b=?ZVLn@mt#P&CPcNi-J;a6-@GXmtPX=Ckw1jg%=|@3OJJ zwOZ|OT^{s*jY8Y%c0cU%gdX%?OpC(?mofL!VYd?@{w<{$@mIE4Vz-M1rssjna*n3L z1J7Bp)27{G3$9Hb@ir~7G`HXVf^56>iT*$f{5WvM@Cf|8h*ia0jh@7=EsMb1HBiA_ z-LCyp1FcZgRuL)^c0UQSQeg^uTh|yMoBWyja1Wcd;%WBm3x-*bbGxZ`c-I5<_>jwRz@Tn{|4Ui!yhhx%(u zk<%r~Y6D_qNg+`#J6qpo|sM zL(H4HCu*zS?7h=8&b}3yK3c%<{OC!Km^zM!k{n{vh}uKw1dpZlr!nafWowPNkZVFz z08{36sasrEj4-)Q(@T-s#=7X>1*?7+z@dbkVDkn_C)a*)$xLJR*sD7Lza*8*HZX+> z@S-bhX`+uuFohR%u%aSHLbkYsg|{xhq$u6(u5~Oi`F43=N|v~WCw_?)mI^ll(#qm8 z4FCx!KI-gICzFm8*4XoY5P;)6Fu4d&xwE@##rk7Vus5*fE>hrkv`Xc zm*pU(J#GA( z#@yX0G|E3@CcQrl2*d`A;Z9Bc=tuY}2i;+lWv|hLRBHpM+vu9bXf#gKQ#4*RoR*GE z9JUA+u)^f8ey3q+Ur0FN`>~-)Uo?G?imw&l+vq~WMf`dJij*M}$gqdiK*;N`DqwWy z$e`Rdt%qLNLL9Oqlk$6R=nIEB8(o*eWI0SUA@WmWNs<#EHs2yjz~Y?lANC6*mebWb zo#BADv3@(#BKgzi?WSZHusjF?AEnY0=x!JQ4m}9+pDH0s{i;%$o(L1tuPTB3s#2QX z?<8;@vb6tH2{~)xNu5@YA-Fs_rYa-7rZ~EaGYbF)Z>3MqzK@^^=4lx8W-`yL_9;HZ zzWoqY+5Yfjs$wuOR5=7ZcrCJ#TkOu$EE|x|I4{1`%N;W=t~>fLP<>utMgA`TGo;JhE)z|~5aWt&O0L{b2GrBuNef11&0szwy%_L*=k5#<_P3?l+ zs8mWCOpMiLMjF)$75ym8M$|?dt)|kX&7@A5zQ!bmN`m~~741Qa8hUZLm7pfgdO6r{ zD(gdyrKY%aCe=?b{l#LO`Ya~t2z9tnt!zs9nkldK!j6bNevqkht&DD`sF3LhwWpLI zODT^8ti51+ZSSQ|w18bLZod=`9SoyJ*QVVe-%Gt3C?K{_5$dV^F8|EIU_dr=sWI5C zB4%FtJ+eb^UfUKh8zq~iK55u38Xmhu=V%ZADz`=K;tJ5+qPmuayXh$>T3RnhX22ef ziv7&Ld)RhwQDm>`=67Ut&RqpmR$jI0&UYte6MNE{c;nyWg@Wt)aDL(=oVWw0gqMUl z5Jv1mO&A2^eM>w^LoqQH{v~&!hS&H%c}2Wt=pAhxpR2tqb0`+-YM@c>8&Ib@vZ`Cp zU$ty`r~>Zq4X~BXp(%7gF+TVphWP9t_=!nAp))jT5X^N1(DIFuEXoArz;cMvFyzy^ zY%_0=p@oPp-j0ed*_$k-gB(g+X0@YAqnM9L!OzIJ@^J49s6hQ7>j0VhZw$@=2`yH* zqk;hZrvK=|`a6h=rmSYhp&7bf&S-QGKrlcI!SqMKDDH@m$W0;af@GT>pt7vq!O^Vq zbE74?Iq(fi@a4OWLIpEqi8-WXURYC=7A5U^hMA&#JOiy0?>&VN!0+%SK^U#o9>!)sbMA z7;=8DQI;k}l%~ZmY#Nbo#k+a`TTQutKG=1@fq{~-)y!^(PpYMHagIlLRxw_e>)M>>Ex@_GDv4+??>uZG%H_WlEGS|XDx5(`1WL0zr|7)WqUTF3?-Dx+rf@sj5F$F@XHByC$$4_?Kfe9dCi%MPZMx$)Q zn-jdIm-Vi^u-Y4u`YlQU9rXhmKuG%V)%ZR!9~RWWtUvw^t;(arx8u7b)#95Tz`wfq zjRksJYU`AT4f?*%!D(>pbjbKx4F(Vad9C@T4^ai0t-Jb!0oGg!^l?C}oLS#FC zK&qn)%zl0xiGi?Cz|yZxvHm=X#k;CsSb%U6p5y5tKAm~T8!)i!{cHsOu|CMy1;yQ* zyIIfM?d@sp7Q^4qIZF?g(%J+n2Z*oVu4dW5P7M0GTbY|LVZq}8npkSx=GS&friqL% zpN#!<1XPPUG;+Gb_u6PU%)86qzvHhV#elxxYvk*4rmOSy1CLW=Yc$2sEJ*3nxSzbp&x$)g>T8B+)u&ZJm=UXrfop= zJu`KPzW?|;*T#TcIzO;wPDwq(g6%rRm8*C-Z$~!qquhqvc#*j$H=>j2x*~iOxpnm{ zDcq_-9=_7Nb%S+NHNEaP0rG1N;{ynO%@X?a;3WKV*GOenzVP^dN7|KX(ozfvDneu&wTQH^kr%p$<5|&u7^^ya) zu6}DbcxV!9385)oP>v&O04V5hcBL0w8xl z^zMk%q28K_h}s0FP2dgyHbb&{Al5+JR?q~2c^x;;V5LV`6z3f?O|Nx&Td`uz?vN7je0~r-ketCjr)PDxrMa2O-R~bo57K99E9)Q z*o>Sp5xoIf?ebUCgS_cKqakA!EO4^yAS$WvR=hoL^6R8-&ueTVfBt?n3u+L;nOg?B5T!t+p{x7&w@Xs35#^EBf ztx{aY8h*Oo&>f^Zvbri<#E#Ot4_sl+Lu?M`g(ZJPJ}%bLTHX6vW6jO!)`ABE$` z4g0TLa6Kx1b#n<0h5)d&2e!fEawrX3rR^nO?lYYPFD)T}4_kbUpLTS0N=t9e7+{H@ zdn>}2CPr+;GRDYnQ6eM;u{y(N-sGDYAk@+T*2%;0XKHkMGeY(BJvo`vEfiJej=-&J zf4Zv~D~mvnoAN;1DFijRy0ClH~nVi>Wg3uR2g!kH@(C+@`#XRn# zz#?=1R+x||f+Lx{G4#dtSiUfP)R9;9Fdrf_Hlf~tE@a`B`*5bovz>jzs3N2a(I*!Ex#p^2`diU>SF zwAQ6fd-{%|mZTH$RELgel~B0WD~HO9h=I%{2UEN44@t8I=Hs|UCT`N(OuGsCX9Z3*A})nF6Hf}v5&aZ0${6tQZN4&V*4Bf zcTfY-e)bW^>3h6Fvj#!vd$K1iRbS&l9VxU9*PhD&A!5UsUSJ|#6Gub)9^dNj6K6|L z)6N%wsvK~F{y>kZo@O3fe16<-R&w$_C*C6$U@2=KofyCLe7zmJYp61ZLKn~~ZNStW zRE7^uny(W_0!I;Ao8pJm{B?;3d^erSwv%Q*8lmBOtMy zC$FOsamc!0_}u0=FwEyf8VmsUB;3_%78ybj<#n*k=-(E*0`J%# zHIW%yb5qzN2Jg9I&TH&D@VSRiKY{kycj)r2JI<1dN_@BW(1-l=%#kjKd*b+4BW}oH}c1{AQnJ<^( z#2J3*LpZ~t`FQ={d)*7oM%8&v9EF|w#?f0{bYc;j*yOOJD^OrS$@qmzL0fIbgQ_aI z+`;O~Cg#u`Yw#nF^Z%(WDu>qY2$WS;X;DufT%j@$SV5)M5(jZ(J+2^0$$J_euLBe6 z%+NP380XD>rdVm_u0&dr@92%PTp|aGhpaw%a~@Sh?TR7yGievw??yOJ0#*V|QgrM} zPFN|*l^aQpBHaaRF@8LU-tkWwC_4Nt-MokoD?EVPp<@-yM5G*U_^tY|^TxwYahGK8 zFz=)Tn>zrTzZZ&a6Z`O(< zt2i8(-k5Ge4Q5($-@0LUa+O3&pViI;IkQ#1KlOJ{N&;u&Qv2hn+wI~xZ8{oZCK-&? z6^1I8%FPQ}!(Gp@bfS-J#&6Akx5*I>4=-y6;0RI{_S9W#zD%B`D_rIIp2sV5`-4v7 zUIU)UAQ1a_m&#00y)`mDkX2QsVinRD}GsV0G#PCDP16y17PxEtv>a!P`d%9 z2=OqoM=Z*56H~toT+r!!Tae7)|o99{Z+>$4Nsn!~lwoJlhO!0hqJ4(5;C(jb(f@ouJb!L>HBUwf}A!)N8jwMkycd}W{gQy4?V=GA(J$0#!nkmj&b42B<2gmO}Reu=D zG0^L%gSM^SVRF*9+kESKxp@G#fxM1&;`n;PcYD7(hwtj@^x8dG#r4@fUgh!GK3xU& z;W=6*_Td@1#J{%uzr&m2|10iUJ#41G91tN_+$ve;8rC*{m1<-#<%H_oYpi0*G0+}5 zU!ZqvaiWw-Q5m!=2r+t2J91mgWmi(igM%(U-1ooB!mk&w_Lk*NCSMbY^-H0A8$j>w zYhQ5WlFDH0kRX?!CvtXcSuA1#$CSo3LCH;|ER~u>b)tGxqa{gQmf8Vy^xI2r4y!I{ zY{pxOWy7g4V0y_If9bH_MeN({=w~U|(Z9egDZ>q!RBAe6_KXxNceJ5wv>_98W|HqR zNpSJGzTkc~!Z8Na`jD$&p;H`g7JEkXq{XIu6fk&slj4*TZQ6 zK~r4+lGTVf)&1)x+qC(^djSYxl(1RB-~pT@MXIhDCX1;K9P4ZPUk6T93BcYf|GLP1 zzG->0K$5O|p_*VAxJFP@e|YZpeZWtW;&18bx|}_GLl&qZif=QlcRL+J7hKW7i%TB} z`I2&0KDY9os?d~n>|l#4x61OLuq1SSD0m(@mAR92!q$5Y(mb#_E2GP2W!4`IE<6(% zp1ZLW=|L24-(wjdv_G3J2U0mceTVBGc>+&Lp>TG?Rq(iWsrg)d;2x%gQp7o-K?tx> z#;+yG&xC^xD^K~{**lf2x*jK+^va z@c3!Lu{r?h1-07Gt6d4I@ICRju0QVINjbqEwk zQ|G{#Gw*DfB8KwpV=p$i3TnbQMzdD6Ynfbd4Xx}rB>!23xc{s|pwSoB3G6C+*zW@a zyxczScvRp|tzzQ=!MhYYY9Pl#Te%-#^+i8Uqas=$hsGqZHXM|4Zd$gk6j%t?e9xnOTw7_=cZxz zETyKp%MV>!e&~RO2Vv|x;k8518^MFWyj{YRW+#bog@FIFGUmPlq1)9?xjQxcHjmvl z+q)Zp{FW_PI5@pA0GW+%HjJN-czu=fBZdP)O6ehb__0!8Z$r@{E%Pzf9$d`7kez8Z zZjhG|s~B{mex~0oRY)o!iWE>MKvFH@P^A9CZsMmhUB7ix(Z#-T62=UCs?Pvi#YlVF zFe&+`fC;+PkUP$57Cog5CJ{?1A0%4nkUDO63LJlj%SVZJVg3vp=Hw`}XH#5n`d3St{0jQ^K+%RV(+sOzX&V-bGC4 zJrE}ctj2Pi>t@d51)p|p-^Tk4zbb7O?aWS3ZuBdf+FqX@5T45|pqjRiLo^(aV`cPa z40XVJ=gO_LfMW%r7L2wKG`G~G$;KMqgb@R9TYz4QmgBAmaE!IB5F6t#j7PErD(K6e zu;l9nV&{g_b;cc_sp0)?##LMG6V2j+k9z>QyHBI?THxU%^{rYh-K`XPd-@R)!XgWn z*v68%Wr%za&gbWx7Z4ezvZmj6_w%@i&o%Jp`vB&<+-uDCPS(=L2JEM5H>hiWW3Ses zko9d9?B|&6%~YqC|x#jymOQZ1vPp19@Z*l3Xzv6oiynStdJD=zI zxYcb|%M50xH}|SEfA}#wVF&+ZGo>>YZ&Z1$T1W)UzcfPxDZ>T1 z?%wY1Ug#5(9la_bCZtO)tVuB9HRikZIx9`S5zl&5chMwziRZ-Ddw(=x{R+P>AgH`s z#KfRjI0^x{wsVm#*ALgL#E^@Gcw$k zjA0~re-@;&T2MS| z$0|gh*V`;iRCsYf%>rHPTGmOzejh9t-5l}hW~w3*XYp3;p1cseeL?g^PPR$>v`jlcfEX4z<6 zKG<`6fz+}5^O-N}GfggGhko9&5K$x4U?h2^;<$2ljxEPY(nCsdvXt~HR@>b>#1;eW zu=6AY1!2pNo+o&d*qkj1MoYXSq=%1NQjMdY8V03UDKzDax$PHvaD_AC*zJ#Yh)M)i zYRmD3R@UQ31JcM2)mArSo^XMcQAWt(WWY{=JAsqIrGd+mI2QD=qpE&y_g7SpM(x_i z%2H^T4^(7fZ~oj>0)?g8zctKPYo7F zjcKfiGm*?qFU&a5p!0&iJaV1Ds6m7l+=0L}^00atS7|icVsIOSlx1}x?ZC|v zmP&F=LWYSfkEe@A2g=O5cDU)81s>sr68Mm`w8qD$*yHdw;(dr}o)$BV`EHL?YEyV? z7Gt+4aq0nw3W%g6n|R^3j!|sUNn=dOFIC`ZnT4TYrwZv!`R!iCDQ2=AJ+GSCh{c-m z0k;)G$?e`aIlEqQjzi5j?evo&pET!WFg4?3pN1a34O4gM6x3lvy~?;^pdT{N>|B-` z%U>hR5{&9off{fdp(MzV2e5+UrT@BkH#8aEiT!g8Rgxw{IZq8An3N~VOE4(02a}a+ zC)jj5P!WdtBrg?x$yr3uPXYJs@xLA6pumvw&o|3!f;-!LQl1)e&lDaozenmMJ!#}Q zC`&G}lAkbC{yDrr(ambY{Ih-kFkdTN8=PZNFEWV;hK60R3(GxqColZ2bAYYoI{f|N=oaY zU)m|2=Qll)r?H4ae;HwSm%Fx0y%@kfu#iEqm}Ui6*?7TS#GM$B;aaxGm5cHZ1q44k zIGK6|_Ab)1IyHvdb}k)k`pDm--YrDKYv9fPkPj*Pag0d+?WXq}*CUIzx;!!<+DQ82 z7XSt(@QxGE@br~JFDqDRAh|CDIT0j9O8ioR-3Z=w1DcXLSNz5Z!VgDU0ph&v%%EHZ z3ge9d;^eeMc0!NEMd#N|(^}+UgffQgrs8DIa{HdoE&v#9nPNEhb2>>Z%0 zp*Wd53RW(azZbZMJI#69gqi|&=>Vz>XcVcSB}6W?@o-9sNGj1=0`EZ3KVs}vb8C`{Ciy*Y6G1PD2bwSituVzXqeJ09M-lpq;97CL^ohON63-l}u; z>u{=jy=wc0m$tyShn_oWDRGvgjfo>y;V5Gk@jaG%b>$f>gnITE^q|9wY*|A$5kUmZ zrp}Zj2IH0D1R$;Wl>k9RfLJCdK1xaFu%LLDEYOHd__)Si>iME#YN1H{!?w*Ytmc){_Xpmfr~3rEd3k@x;1rcjg78 z&&9M8%nBONs`*_>m3@Vt(7nqEey%4P6x@S9vBWAH;1Qz{A9Cz;Q-wXY)_c zH!^g-sAY;$aK2V|hZc)QM)~kua#W0g+ZDeH(nGFXwd#F@thMpK!K=F~7#& z6u>#82bvrpI(&kxlsRRNHwi;3)WQ*k;%R0B;))KM!sHCGhhcDP_@X5HBG3r}a;$xr z!Xf~MLR&OMgy4=`nS4t~e=!p?r|fTxs2||U{OsGLe8%9EGch2yRSXcC=K8Xx9LCXu zBW3FFTCx<%9=;;-hw7z@B2C^6muzKa#NHo7~7KkcuGkr=B9?LGF&|6 zvIu^n#uBkHm=$c|yK6)qG#`<73twKvGHVda}k<}DD?a3aQ?xXl%VGW4N;LhTQ-i05TVzXTsnhazWcFH?$tQ`$OSl@i%sx$d7bL;W*WFy_S$fVI*%*8EzN8mF|8C0orQ6f^ zJdXBlR)*fo711V=t%cfRv!?CkR5zNgx&0>0$=zV^j^A9^qc(}M76JD(jsGR~UyNja z29OYb9qF;``{^ueoY=|;XR}pc7Gqj3p6)GpmR4gbmPd|uaI%AI`Z|-pyr*M+r_}D} z+Pw@Mw*Pd2?V@DZDVvM}vb4u8C+fklBd9Jm+!Z#h8L7r)hkBndkG)df7RUw43OJ(Z z2av-X8XwNU)J4E|UBqTSZs-{xka)0W$b#4Yv&rUj-UXF6XX(Z5E!iOG78vVd^+KV& z9GgUSF!M^Nag)v=rHiBcoq8nhFu*L65LyWTD!Ny++N|ArKdDjr_pdF zAJZqVpOyskyBBgwMy$%TyTL_~H$C3oKhueu(knv8-N&PAkJ3ul4v$jmVVPCdTc=-` z=~HT1+b2`%YdS-$Gfjt5%z3h0bVaqJ28#2+c}}aPn;3swF0g+Py*yR!JrA4!?)@nh z%5kg?KcKPTcBdZ_Eil_(sJp7(am9l813U~k*p~(N)sL%oYj*RLPA0f`wo?z3w3>7D zT(W#$)2|5Otc9*!y7Z{UHs{ASzxs7s;q~I0W zf9ec&%Ngj}3_LV$V$GkT;pnPwPCFA!`fCNX)fO^xGod9_yaL#?I_t`|%Cyxi&{K|G z^~bwpP;Unk;FT)VDkZRTE3Yg5a2ZG1DVsN@vlW(3RQWZpKObmr2%%B+qv97~t_HqE zEm4@vFQ4C!;DYGkazv^mr6caX7v|5xBSmzHSNQeD;%iksnYcZGRiXB{x;_BzRj%5$ zywO))bLts4i^$;j)V;mx;PXy=k}o0H;=G}N%uNEjO*`MidBZ!%@G}i2#(+ov%>I1X zUJ^$u7WqAowA|#Mw`R|oAHikGYOe^_8QJtvuVDF7bUJ5*?83D-dD1$bA3?1HToe< zdJ>;OP?R1KKmeo%sLs(xehsnh-7BRb#=Qm71j!oCzm~8_IlK{$uT9rwj_5FwF~BiA z!L*FIlaug#8WI}~fK5?uKHq*S*Rn1Npd;8khy_Fb}5B`G6rljfru=urroT?1#4@?QoY zzl!&TSoK0egZ9ZGs0W8@_4J=7Tz~^BUknLG9(f4&YTJnd46WWppKIE?zl$7e^M8!cIf> z-BU9B1FG$dLtRLj!j(b()6~F%*-(7CdyxU3D%pNfd>&)|p{|s>SWa~xVCfNTUQ!|F z$6DP#n2j1`gW3{xRVv*Qm?s>^X0#*q#v6bs6j75`QE&DDOmL}7nJn#EldVv?Ngkxe%Um_`e~9puaevgB&P?z;eRoVev>(y@A!+m&xeR=aFzy9P@QHx~Z6K>(_+8=Am%*m! zc09JzQE0yoVW!2p_MWI&7Zz;?y6x7{I*J{rC#X6xgzaoC!w^X9U#687b*}Z9(i|)7r^IWA-@XPZ$4iq^ z5$YVwjVbCZmdA_JWyZMs$8$3pCeKyLvV4Up&Oxbyf|lvZO)9*&(Hbj=I&ier#wv>% z?@!DY>Y{EO%*`q4%D;@u|I4_dVd7k!EUWv!jE07$l9!OPrH%5FoCA~j&Gk6f9s`f( z)=LdBcQ_fE9j=>7rr2qUi?RaEJ=)vlRK|JkqgR&kkteu;g?gA~J1h&_(K7|Ms)CO; zzliA@u;Avx~Ult+n&q&QoQyHK9V$d5Q!7?L?xq5Q*OB0WE!DUJWV@eaxSL`KPkoEt9< z%;u`XP81JJCX)Y%>Hka2WaRpfxbeTlf0#_f{}J>5msrS1{Xd*o|I3NRMD!oA{C|m+ z(w;fijG;?(IXC7^(ddc1T_8@fm^m6!Q|3`N&!^~ti~7C{P$Sl8u?`~%fd)Y{c*18m z!rHpRWwKk7a`%YIt;QNkM>vy{WsX)x^Pm~rP~4Y*S(+9Ff}fO-3F$VE!LgP3(}?sN zbGxxRGiF|(a2s()0ogCFy+7!mw?Dvgl7K)c04M+e01yE8N+Rm4MTSml7ytm=aR315 zzgPd?m!X5PzLT-Cxsb7mxsAD#xvdSIg`=&_|6j=WnzP?hAppR8I2Zup|05y#SG2Xh fxs8&swVlU0oOXDc#vQo{rS;)w$S!ua>p(7{;W$ymi)#Ms2##@xx=)`rf)(bncl z`@&&EEakgbkTa&zYfeq-bI_q(AW!lcO2BZ|&x|ay!aK#g z$vfd~`fMb+2$x(q0GC=>IR=Uad#kk9@m&k2_w&RT%Klb&BzJs_+l>_7L_H-d>TIIl)GW!GLYnT0E9a3f%p-A0R zWV&~*oHBD~;M64dN}uO>APjOHGo#ur=I5w;OZkUk0c?I1$>Z(M9jXkN#P?Ww>GPs( zF9wx08iEtC5;sFJRO0npnakcUN3GxWu%U$L5S(85rR%VzBRFXnXGi{Nm4uo3E08Y< zv9Fwo7F>TPc6PKzQtz_4Yn{jlw%SRih0d@jl{n+e$C}1Wlw_PR&)XwP4QR-@l7)uJ z9tPrSX1xmsvrO(J6m)-ndVk2b8Q!qjhdbHlpJi*AZn%d@ufS|`R8pkWWv(`zO+sdb zc%KJ)b8xez38wBw$6ksVgTw$bX$ zLPokfF+2B`TkQXkGEMR5#hS>2DT$#ToQE4F`^-_TL9kS~y1)-i3#{JNMh&wySX(91 zh0yZc(aj=-C?dCcAPY+z2>u?(k)S^Cu?d;fI(nSoe?JoW=m{k{Khw23S&FU!5)K!T zCNap?r}9aIM8?1%?jr%&jVjJPTZ9dpAMi(mqYakAEV?IXmzs-?DV{|ckw`cvd}(Zm z2Ojq0D4EaZHZ4C4jWP(EA3jM*Xo>wTBBH(Wr_3)YZy61pC|3AN_wLG)e)27Ng-W3D zu7H!L>)wq}Dx4^1MEyMcq%`W1+tnuuG(7~M@m?_%{AgMaF^6gbE(W(rNRRyhZ$y0H zSJFMCtbTQeue^3zmIj|^M37W3zlGxU3lm}&BtlRNF;pGyag2sGtW(e!G+zR)i%^de zI&aT<{#I{R4s;}^bI&p{U7Y8sF}e6QsxH9-*2CS-!1@ZnMdI#VpOUd!TclxZ@8m10L(n*%bgS@-k5%m9*YjO``Ou=kpWj=Uy!bsNXQ z`>Cha?Z-R}klUsj6As8J*?mEHSiCUhNoa2E5z-$K+h{tsHNH7a&#?RUBQOS5$fuB- zN`l^HNeKLh&UQ%nT^7J=yKQi-lm zVb8~?0;Sn@+Ck*ph6B};qH$zU=I_5s10D=gg^C3^0uv=%R(!?IZuw^DkTm{eRHRG6 zBHG~iaSMNgia6DgXzNWiv7yKU7*@r-CFaF;;?7kw6mD)xsw9fD6-E}Umdz3C7w~3b zJG%?7Kg2LdCy0WV{f$Z_ow?ke9}B`|DD>8%cEVmj@w2%c_eH5r@s`{69I;L{=@q?8 z-+B2sxj4@WcrOX?2n_lzR9j!lRkD1KLHOnrgS3xXmc}W6M~Gd_4%UcItrQYL+Z&e% zPsU?sKfDVdwCycPQ$XF0`2EglALxc=nNpR6*)#rhQSq}$?k z!*G`z1!*u`D|B2n@*BMNYdy`eyXx4#W3Q6kMJq`>!#HJheyO|P%Au&K9@fQx<5IvS zzJ|=`ipL8r7}TYsouWs;*T%wLWF*uZSvB zu0oc45`#Cp0h|HTlD*09)vG44`!{*uY(5M$o6%uZ;nO4sl)2!32U+7P$w9-hU*bSQ zH-#QMmd^e?=lSjX!SM*9tUyOuBo!A*tT;He-BVPfs`MZg8DzQx?B6JfI_tAg<4+q~@0hpr}3oAJAD< zZ@3b4$)v$&I<7CTZS6V!hG%KBm|i_EnA&;l;$CqcgFumGGA^RYG~JMuvJLov2luX0 zSvTdiX#db<7n?qqDJ7LrI(|8VlnVH5rfzyV&_p~7LQ3D;;VUn&r?n(*n*eHQ(%{2p zdDKq;HfHN@q{4>UrBlbHeg*8Lk7lrJ)$H#`O@)LdFb-7EPE> zB;}#sf+?gDpviJWMfH1apq`LNEI=Ir2B#Z4Dx;4Q#3>&|SX$7!5lb>2Ek-M_A{lV= z!!aqh9=IlYE=yJ?(F#(RxCLU>1H*6$p?4>K0Sdz?j_ref+z zQcj=^Q2RbOTis=|=C*?lE>0{Mm>(hh{pI%KuHkA9sQKdyV>@KBnA=SMfw7ePMDp2v zF3;KUyhPAgaKVzTIXo)@a|5QOjZ}r{Fc~oimeDU8bC|Pa9d-;1mL=YySXeqQRF9u* zfeH2rXNG|$+Us@&f`d$IwZmw?Gb3A4XuG2WOZ*cEgP&VJy-IKT?yoYA@Ps*W+#Db9 znYkz?=bp14{-oS!`rw>{HQ#4x19AsSdT{(4?m*nRMZRkwTFn#2vu#$3uXK&5Y3b&t23hGvW;MQ2ap9TU~=vnl5=hd-gg30273BgH#SleYG_n zW21)=$Zn2h*`9!rt_zNvtrz5(O-uF=Fm%Q00a&|b8ot%9CguA54boLhq_xVu9K)tu zbv=Hp|Nbcx6##?}N0KAZ8@Q8W{(BbbblVe9CeR(hsqM-HXp2tb z3nkm3>q%5twBgD3wpA@6;nDRwSBNOUvNwwx=cg}YDn0OP%*#4? z7U&P=X5EF-VEuR7Vl09!=v>zII)|tTwEx(U1xSnWDz6!|OMc?Qj3M!qs@}7nDT5~* zd%W^+@F*Qgf_&ENxVtnGPi{QZv~a3%j<0BDo+AMzS-3N1yfBJWpGqVOqR^piTCX}XoWWX;Dn!YhnHgF#CM6UR3PdF)3d`&ov zR+@lb8w}MVVd9$ckJ%R&$42f&&Jl1^@T1r@!u}GZ=cnyc;9q`RS<%q$BObad5yOK9 z0CM}7jSDo8R2TSu2^VL{W`DCXsAUUEBcGktaywLwYTkzrl@Mda9_pvP*D|!*4g%BV zU=RQ9Fk%197e9h4P!Tktg1VeWgN&nZIUn3iRqc)t4+AHAh~;dFh?&RNTBLkyv7kO} zCl9d=N|3U04mgyPP_nd`3=Qgy3%0qe#%?sW4;(G=?Pk@wd3TDI-X3Sy6q}+9Vm&HZ zJ=r1h{3;Gmc5jZTxJA5)d?)O8#K6KL|Gr@FcY!XP!P#{yZ-hoeWB;a7Tgs~EHn)!4 zrfI-v_3Z#P*i^2MND$FM7|EL5_4mu|$Y)v*_TOOB2)nW4^g}n>!?vo^z8VUT?&LLh zK}ew0cu0c;+`HSKquW1%ihShk%jsg+{&1Q@_bj6Dm~|fkO!C#&@kC#XOYxW<1i@Zp zyo_J!zo3A`ZwF%XVIMZeLH#$uV6ObyCn7@Z{7qBf6d8vz_vcScLNACXcZDv>NKHkp z+M}@Xmm9_byrU{GDmjd)7LeME4<%1p&M~xv>>!yKrZa0klgl#9>p8Q{#Mg(V3=L)YWgJ3{bnFV+rU@LM5i@KgLQ5iGk}EKTG_LJc&|RPzUt!t^pP!#rfU&j6+u!MG!NcVB0|}Ni zruY;B^`v1dQPIpshWec$+yv|N;kcxhiaE4(VDaY#Lkyj21{(E{&3gC?1Wz&o%dUo( z&QA;!p^y3O!uy5`Cx~EYc$Z^^Y5u`>oK^T2Gh{M}U`QkPOxdj;UYg$1u?B z%=mregISbhqM^_S=S-avHwEW*e#U+bn2B^hJX$$+p?6;OSmqO-q(Z1Th-dJC9I(tO z(2LfZrT^~8RHCQvRZH};=T$yyxe>ECV_BZN$Wbm79+!qSe(cM$DK)v$rU(>a2<2XK z%mEDlCHF7y;H!^^_3pM6&>{o7_+ROZ+X5B?PRkdu60&H*s z@@^2F5>{Ep=5HHm%_SMzn5zRVzVTumNjc8GN2-l7nvz48qN>zxOpY9DMbCmeO!wLH zd>mrB&iX1?Y%)@`CG_io`3>@v-%pZP!$tauGr{6$zYkRG6O`38S<{%%haj2^FE+8~ zB(~C!w6!$Uj}sj?TttQp*-bZiJbnNtb00r+Jm+}eZzN{ zzV|m6lf4LRxzn83qrV$bh@pU94seJeB;Or1wE@^m0iX(h*=HINo`q(p%lxE`0Nhk* zBJV~cZ5qN%(j3p~r4q>x!JY`O-d=SArrEH_Cm7_o~J}q}a z6Qd-S?IZUjDp;DL-G4^1tf|mHymGS?3J9-;n*~JBx<%Dk7me`H>Z2}rq8JS@xQ}x3pHb1s)^rD*gZ`_%=>S%{oE-X@9@fn((Ylth%aXH9gLh*@ zR2>;eCS+yH3#wjM12eoVsukBYRx9@n0%OhJ^9_*p4 zvx~{0C7r^7_bS#~v}oHzHt3s;W@Q}#Pd=<5bnDPhF{?*rfsQoOresAh*F|%Jnn$;2#YxJBI-aLrZ1m&?V4{61Q1v1ZOhs#hHNvhk2x2M2Cs; z1uktqMFpn_RvPmFQ5uWXD2_s5ZzZ~tJU8c7ys4s|tW4M@A2HE@*5-_$ks_DO=2a0H z+KJvfi=Yj^*%lbUh4f&KNi0dUY9O;}AT>tVf?aFCR_$|DP_zG<240%1NDE(3K@gyw z;@apB1~6gXa)BD|;DC1O5G>&~4A{=seT5tL!UkkvT%mL(%>kBei{A;`ZdW$cjVo7W zc)rQtmy`s>)eraUaQp88J1ucqg$NfYp^RJairq4cUVu_Tx;4|qsU z#Wrw}?H%Q>2s9?!0!3{bli+L* z>b!M1+5?LKnii*wX9~$(uhx`hvyYVJLEh~ZeRsNSl`%=bF%6+()^-&trFzfECm-^`b0P}c&=8}xrh$>SE#<69t;JL;@cvG~=o-w!yWpSP zB3aR-hHbLnf9^{RGMS1l(`9(_V01DMuY$)~>sd{W$6=+dGW9L_TIEcxh$Jl|bZkIx znl!848dUXUSgs<8m0lA+;+9km&^s%4aXBGD7@@hy$L2BWtN!NS;3Bg8+?PSRk`eK6 z5W4)X^btU(m5CUkoN$RvYZ#@SCM}2_gCiH7xsSaFC=6FBjy7uoZQbx*bV6T$aP&UZ zK&?P|K<{Lmu)=3IkjiWXx1$5m>42^Vy>+?drbTm2z^zt6Qi$fPY-oC>gD|_N2uQWy z-gN9Leg85FrfdER!p+;B$1+Z>FHU@KXHg9giDpLQ#2ge8a(7U-{$pQvOpmy#c4({> zRi7nU%P{a#4siY9F{96=BYj1T$($-}in0Ca4Y_8w!krY z)9r&XAUG0!y45`YvQEZ%O=K<0{l1j8ffjwusOXW>_}h-yKJVe4A?%(bWVxwyD_DGs z9iP*1d3qKrzAf^GG(Ux~>Dq%m+=l;s3`xG0rf7`P_w%2;DEZLbhkA)a-~9#9$S_Z5 zt6t})NkPhI)A%#s7RGZQSpfH_Lg6{@9r-pfLTm`%$+a!+N+n(H;` z{cR9Qd!Zc!^>}n^+FB8Rxx+G?$VD>>OZG*V;#LqvUIX=FqkHurznLLkBUxKu2a_#WTHrJ{U8s|C{~=AC z6plIZk|Yz4Y!!=VAv8wruJ>nK@Rw<(Qc&#w8tq1ifii;ROAY5FxkU_9a!^70d+{j6 zR<2-uwFF;?8ar>s8pAptwwOPXi+kGjY$qzrjli^m+Ll$WiL|_%c~1QEzF@qlWW}kK z)imVom-A~gFph0 zCC#H?I_nZWJX}e!54&RdI9GhC(-<7#!t^Th1bg;i`J_(N>BrvZtiEgCD9uxlzz8nj8Zmv_O&0-&eiiFzaByQK6J+_wT>?Q zOI_s8Uh`@Li4Wx$BgOITRZnf6&up6dUEzY6((_;I<*uu4O~Mk&@BW`3Xode@7(t0= zqxo701O!Ni07Ci~Mp)~c+x#}RwzK*dN&Gj4xa_`g*^p?Ud;O_$-UilOY7|jPMDFLV z)VNNe?UY(@k=#(a;xbj|TPKW5nk55f6O>Y2diDuZfAFnMH%)`&j%7e{2SyeMkhm=X zfV+QtVH$Mc{J2Gd=1`xo*uO@!gAE~bx$#kvt zJbidcJzM)u?tnu^cdXSeX*PGb@5YUf^^Q7V81Z1KGdKLi_Pz9Ml+Poq_OJ)K{)mx!MN2va4yD%GBjqfPD>Y) z<83upe?LOuPhXRR^`cCc)VR`i_!-)FzYKNeZ1rg}dtAolvl2{wPhn4)cgrgyzdn@9 z`eeL6NE+MfdL1=xsz%UtRrlp|plp|B>wcy)B7`ZkLOi7JA?+iMroD{}!GH{A#8@`2 zVGs&4Fn@pX{zzPXj^xH>VgIh~CTq^EE;sy8xY~JvJ770x(_tV<39QC$#4-5%$mah- zmZ8a96k-3_YU{MW!Wo2v4VBr_d_A-~k2Sx05f@YQSHk%5GW^jC3K1;ITr9LrhzlNn zf^I|NQ(OCzsG$seg)r15LF6GhCS$PZa|CM#!Gank=K{ov4~_U@3JVEE(2DnE=M@b0r9-Noz&Z45NCYbV8 z6C6M;=nD_LIh8`QgEzTQmHn&=FKD)WANTW_n|LL(l*gBP6vL0rqq@nXROMh+*@x4F zmdDjNE`1<5H1FTFwLLpet{Mb{Qjdfyufe^U{R{y%FEZ8b2qPdnRfNy&-~aw*{l!nb zeDwfbZ?7t&t}+2bx2WCexwI8nM&NY4x#qo^yJewswZ4_tqM7Y^&3_UkDJOdVn%r>W zu!Ht1;i1@qgwJg~GnU9)IVlG7%SlKN&+n~SY>!ZQD&qGHW?99km$(z#Q+7L^OBRZi zi8m3W)R@*E>y>z?&ZVu+e!8#FehRl~im{EcD!IzGjT9KDob-_Z$Kj695ho_Cz*oU;10;i5ithPL{Wp3OG)oPwRDt&JtT^aGt`_ z7Lqn*^#$_wOLJi|rfTvGGz}j$(=Wy0@=kk-DZrK)hu%|)63Us3l+=`JmK`iOp_-PL z083F@najY=q`g%jl+IZe0Qu$0kSGDRt&Y%Br9{!K>_8$(}^gz-ci+Wq~=m71=z_AZ= zYOwiS%0keue7-7Y2<_74hDrS?;^jKA3|`Y!a~H|c24?lP=8)*1k(Vd+wkI_l=xRk& zy}f0WRf*FmN+W$e4q0@YlX^SNIy>rrD8+?yxGAw-O-q())oRwN7faPbzOttabSsxj zlTu@oo8XPg#=7bMpuQw%s@NkyXf}Pk(3i0jhX!b6(XS8d?Zk8cQTA7bI|a|1OV)y7 z*fd?L|J<}#csJUtUzu!~r+>+yUsp{JaP6#^?oE@_*KJe4)Y+ZfsjFGaNY~l1#|C#A z&~J_#>uW5YZ*0YEIqv4EhaMHkUuyUQS&sB!A3iHmpL34Yvqc?pu^nh5=|pyw{?7fUjr^j*grX*mK@XOUb?x#p0UnZ z3)rH-=-0gd2U*i;DSCT8{}AASR>%KPI;`g{AKzI=n;^|Vto+mZ_Zk_knyY%&teZNz z|LLjq4`%JDof-HVHPHXBzuzc&`q_+B_ETPfAURysf|602OU8#%(@ zahWwP)PV3d1MK0bAhz+S;xSIPOaQ=(zl!a@jj4P`|a7zPLvE~0K2r}n? zf_BZ}qyj+9HEA8R-I{`~@zYb9A>>MTt~i>v>nxWwx?9vQUaVFTIiGY&71=XQ_&2;J zGt}n53+DVmqCVAZc)Imb^$*E+M;EXcnLH1*#PMnKBWX*FB^_sh!6*yFGgYgX$ozV7 zfbIvKG6M6LNSr(Cck5aqbaVc#F~hwf=fh)&Vo-dpAG^Gzg0vs=l$Ps1!oQ`V$>Q;p z8?{);DB=(L>Vn(I%UP&N4;;xu__`YSYbB-=C@x#*msx*&iv`Lu|g&4G6P|%o1AT#kXfAY>yLCx4IcWy+3>@=xP)g(ZJA@cWWBG8MY)ADo9s)Ofdud`eb)UTfX$q$ zZCg90$W|e$Q|MmU2>v&mI<||}`+mOQOK0cuQA2f#)Jb>k9H^sYyNXqF%4frB9kf$( zh1B*UeyoxiO!mIzVQgdh4}6nZE-Y8IPoWA)*bwNecsB%Qm)KO5E0=X69Q>n=bYj=l zC~@rwBFp&3IEk%zuBZ8&3r^gz^(b%}9sUhu2{aRgwsnR}x8X9dvz$|V>x0DM5>^12 zd#C&1)p=qnda(P=iYA#r$~jrvL^vo_6f7|6R)Dw1Us651#ij18(fMxd|45~e?c)8< z+ynJnh1Z{Ss&`KJIvBtXlB_eETeiwJWJA^9g#Jyzll1-^Rxq*ai|79NX! zbZ-$`8)q|OkPC=c??UZ@_58}Cm$@v^RPo<9@q_J8qEme8^Y)Vn5C1@L+KZ;$k1x4S zlX!g;!ee0usV7><6o?HQ|E*-$bkgA%6HXxVtMowpGj{H&lBK0zcZS*9P9u5J`s9|O@Qod>50uk~_vT!yp1?5~mN0)EWm((v|WI_Da*>2ykkg2Wd@Cbe6a z>mWhPckF`D1;pC81U?A$3DQ}yRMosOrQdn| zI2F>9d{Qsx@c;0NF>hd<;!x9N-Q1nmo5njvfl=``=wzdtu=i7{>C78~t+ z1)RPsv~;kw|636pj$7%qlSc`&nctaU;!vz}#{yPh!ccxL>)Dp}Kr2pE%JWr(-u+o# zQh9>|!-UbU0e+McOfxP7cIcg#fuS5^@^>8rh_%G$t{&5qtu|zjbKf``2)NPB&rtF{ z1^fB#SRv!5Wg=y}^LX~}q|kO%F(g^@F^&;|h_jvt$#H7^)fYF>P?@k3uu>fnr1wvh zHyDxu8}WJ%YKz?=11j3E1%S>gcon_cY(uZwN>etCSR8S$Z;Tj3BPd>GyMvasN|%kX zf|>A;DC@t2t!&cbZi;L%Qe>#CCZ1*uIoZFvCFIyEqE8r#?0%a(-~dXxsxJ&(dhC|= zn+d&)=T44DeP2Eb6)PbH{ul0zT8$S4sGPKODq38fKYfjOv!0!yY9I{g*gxG6G zlVG%)D3%0Yq#(VGRyaZXJT&ZIyF4sc5nRxxusww0AP~%U-$PxT*W?O85fR^s$t3Zf z>Eq=wyY75`yhbnpGr`1`S`M7nHNPA7*kavfy5~g!4_3+vCJMpXl@JL_ z33p2NmP|M+;91xl)px(EL4k{exDB}OtvgIDH=~gPI>4bP5B!wHY33`NV>D`++gIz_ z4!CFYZ4Vj69ntC-$mq?X)T8|%APe&L90gSbKiD_pHVuIG8E``3-1O|aBbknz5R^(* z;g{Q@Tnx zfngoMq;EI+F8Y;zEksGLOC_9VR{nTT$x^Xh*3FkCFdblUY5|$VQb4RgB5i6g3}fHO ze31VaXGyc&{1R(4%FJu@v1|KnbgQm@hF*35ep@XVmi{J|7wJz{XP$Rn^O}>~E|yhQ z$xG9n0E|ow~?Xx91H{TbrT$^`<(lrZEhmg zb!`0*fg(Bm?i9Bxm~vl^qFA^z;kIDG#;^T`2te@- z24{^+ua57xGK`aa$+}OiD%Wc+zPg-e!)0<9rCR`c6qLyfPiECh)>Gul;J$`seuCOU zog6h>h5nX{V&}shj22#9@7qb{HMFzUvz5nHT+AKdgd=E7C`+&G3eyL7QQA<9_j^_g zueie>hW8l+#*wexzoVlGwWG_o-lxCB&60*=b5UDEfU8KmS2-c55IxaM!7ffRBxt|2 zCq5rDf{zQOlAC*`)b?OXVov-ioC}LdM*i|#pD9MQrSK0%ZypSv(58*|t1wWnoNU{~s!qf$=#n{GAE|MvscfHiS{35ec3>D;c^lyYX z*<0}U^o|x8J_0%?V-qb}VFF&|Ds$F2b#cM2gKehiWUHHm@6+Ix>$PF>A-&%O<%zm1@$mFqg;k0yG;tXumvSrQJgxjC@1EQ~czX(d(Nlu-(O3abdf|e1z<5U5+ zvvs*_vX#~^R&^T9kd`?8;PoSdKE39Q&NKX@L9tT6W7_OQF43X}M1V$8|E3edx zAYd$1*KrN-p;K9n@-kHbIvcBx=~RjpW-8y=rovgP_lc`SNWBPX(^trOvH&|KQ zhymc9Ff{MLQcab{TOQS0UK+|q&a67LVDV4wu7DeD(-lPemjv$cGT?gixJ)C6QZqyU ztc029<`0ADJ2C^b zbOB-3o_pb&HmirMsM;Ihfx70KdKWZl+tofVh!NHoS3Zc(8rFQ-ft~Q|EL#%LIiV~W z8d)mk%*!jBI_Fq*;eez|nlY1JrsUYhwGA1f#V~^prS=VOJ;J94u5?(O;po<0@k@j&ld0V7bpcQf z55BqTB(E?#8dIhJvIl$HZ#a1QSt{O~uVb!89F4qejxUAn0MQbBU89^QWW?}Wa<6F# z*q>Ki43qZ+T9A?PbP7V%U@A{vST=FDnQXzlwCNjA%ub3aolx81ze8s|MmT``Yj4$s1m$}5y521)zZdV4xV~j6c3I2 zfqU}++w}YXi12*k9(CM;i{+CYyGA(Hf?MeGzRTvvSmzVlTL~j2XHB0KS#~6pA{g-i4TKcj#YcTMl!5Pq$N(Da~CYZPo&@lFatmfMIJ zA*|wGeHYX448V9^&I~A+s z9`yy0JNnqrpok7H)8oNP!9rM?{d!of9`LC1PvX%uW7ibnsQZp20Ws9ntl0Wa-F) zU$gW;D9sHgjr4dptT#evU^2lXexGs7OzSkl0<2UGb-@pJD|pD)ATF1F=XuUXU6DTvZK_LXwRZuVQXl!7IU!KSOIEtOf&F!iW2EN5 z90^Am$Mqbm+MYMGdQ9_kFhY=~zYi2lj=M+x-hcDx!G8rR_a0!)9dAbtnt45i(7oP? zz~npSa=6wPsnO22M0lgBE<5Op_JI|s59YtDCtVZRIThEF)~OHYPVxaWc|33-6qNr) z`e9tjJDhlX#MiT>IpV-U@Qmv%))Hr91-AE8XoGTHY?UZg@5RnlxB8Q6L+dDqM?cr^La5 zM8Ce3hvs=8YhyYW!YFR``v4b@0mKKMc4{fLtWyupZ1Dk(6>aYM-gW+r%LQMFu{BS; zIY08pHvYJ#jF-pdj{9oh;e#%DMX7C^+j!^* zZ@@2Vaed7Akvx>(#24gNdZN;h!4y~eZrWM=i+0PP&~1NkZAV^^F|n1=Q|YOWL3z8^ zb$`WKKeXQVoP(Wy&j&G|saDigb%v$rI{)WbOgkcveS|>+UEh!Dx10$^qI zrh(AWG#5r4JnZFW-SAlJL6Zkj4d#|R4|pji#w$ABWf$wHkVqKK_?-3deJ`tF=||)%SI^VXdtZ*H2a~CS+}NYuB_ota)%2Yw?p!L`X-h* zVs2T1TZ#%gJA>n85FA;El*d?wbjzd!_nq$<1Ki3DGYGRoV|8Hs|v9=uH8FS~SbrOfSMK*mFi0sk*7$p*W69oCsKfser3u~ER4~O9C7*b>vO3@FhD*H(m4(FBgEUH**LM7|a z`M}cCXd8j5`v!_I1Lpw>aDRZ+k3x_VbBkF-3^g?95@f5$h~|Vo{2Br_m|qilgS|u* z%|PdTFS)PB47lw#0|o-OW(B$cnwQ!+E-^0lsWzX*@$>i#O2p6H)N%l_v0L-h;6JHS zV@YpKICElji3O{r+EUEr;!JS*H?ob@{Rb$aANSv&%)B79q}%9`H36|k`#~9TiDd2q z?#HMV>G?rLvC`xAjOlqn^-eXQvmAs5dkyH$6Zb2GXZ=};W0uv3r_L(BAFcQCmSRYyG;Xu#jxE_U2wTO9fRw2&e7$U7 z60X-nm&cB+@Gc%d?}*H;;7@z;unV@1KK(X}FLt1g2s-p~+`x^Khfef++`8h}(jwc6 zi4~xK0urLJd%py*K!mX+gV>?TAzcse4I=Z_w52jDNW}u62jivmemum*rxYxV@uT@O zXaO>o!Iye;^1x-x_(pln9&RVCuFGYQTpckAHgNapn$xf0@z%07XP9q%| zeUjne9I}Atgo`r9_aSe4F(g|`M}3)icnEIEfkvy6G8>jAfl~B950zDF!nOUTQte3{ z-N61Uvz;u^HS(%99yTuw+!dBM0WrC7)vl&R2eW#6jQz794!lK`Fp438T6tHj@_<)g2Yf$iPvoD3(g}%1imu*KUJ^pW^ zNw2uw_YOCM^c|aLWYw!Q${dYr^dmV$JjOh72sj9b-OnPngPmGax*aKyLfjTOaH<(8y8`qED6~h|o6%`Y|_AV|ZMX^OQ*k>aiH za~qb@4`M@w)tN1-+HPu-76_YYBpJ(JerLHV$VH>byBhgZ*Vp|7;AgI6d%QlUx)A{i zKp0Ks(pSM80li;D8QEXSgWHO;1rReEB?&{n>s&c60~?an{B|_ zS9QMv6M2CQQ5zZkB?y1bktQR@pBXN!>_vfS-l@Mg4t9bxI;g zcuC9IXu^KZS!4msJ$6X+zS`=YPV~EA61L648nMAB1YHl{*m0d)_9>P$W(E3Hsiw{F zCr3%yIX=?$m_3b684$G??8vcf951N^Q*rjUETP!E7UY|uc#-1njQ0bp(|KrB$|gvRe38xrMp1aI*;Gabjfl*Yql+r*{;_L4c87N-;U?v zwM~`!6r4qcQb&^$X?3EcLQ8t#L$BlGU;B|=fkpjv2U?0JNfm+m%ZlN*Q&&t#-nTeI zSE=TM?)OYaEUuKLa)Zw#h>Fo%w5Ev-g)#o(ccy5;?loNj?H_Ods7R)HIzh^+Pj8{W zVe4(^MykW9SWgbkfm)&RbDUeKQeV~sQYagj)diWNTfOuOQ`|9tOw0>DTxW;KAhb#B z30JWTQ{|oXjYzIEnzZxMPN6Lj>4>u7H`%&B6cUw}n(q9=`obC#^B>D5)!RzfnuT8# z2BZaN|7lZb!Df`&1;;J1Qc0BP)I<^8M@yfFk?U@&SbGzxR$RS0#Z*KJ{r!4ULzk@A zm{orR@ptArAi6cplT3G2F2znV@q-gveHov-y*1?N1cPbNr{CR&!|BQ_REE9X;DhWr zP|T6vo^->%coGFAY4LNPgR)lTa{QY^hO@~wKc~bJ-4g?s<=iCKegb%M?mt~T`y@_< zui9GifbdUt+W4>sgcT!$3_IAnUpC}cl6P`-qH#JkPI94)CHdkfqNbd~aYATm_={2l z@fzr$B1s%vp~@WT&wA&mqJtzg%l)xAtJm}7Y%_?O7(CVf=0j=r$D+&-###)FJB8JY zhLhUC0m7@we%Z=u@vHGs+gpC?IzY9RvrYUEk%-V0;e+WMiN$bWf3s{^m$O4GwxBhk zfb-yORO{klt;Eg#&;)J0UULUpg0nm*PQdU*KMEg3o!K=}E?cm^-h?~BhCmA#g&Kmh zgA!a#G9+Wd4G02-aVRN|f_(MD?-%46io?;yFb19rT@bPkecgiAAu?x(GWSa!G5uY$ zTg73ia_KaA*7(w>TsiN5c3Z7PvYfSP#wp?-`h-)Nq6X zyOI+e0e`6Mw|-h64&yX**64kux_dh>jxGkQ+j zA8SwTJrwk4V)PimtV12BJf8^cVxOk2)%FcXE(i4|N>5g<>hQs5#IZ5;u3&AvV$Q<2 z%bXPl%eLUsuY9>9x(YwYG1q6QytTn1J=%l6@*+AVeXVH7oC1ar<$DX~oGaB$jUTe$ zR@Uf6%j91bmF2%x)Z;vLOIGYBjUC1-jm8bLmi0gn$g(Cd5~Y*6Ny}(h9h^Xgj+3Nr zBNgyU0&COMmc2M61JQPB82q6{EZws-q$qk7_=Jf8%69rGy&IH!di-YfHWB{Do2uUDz;ufvMs}X--M@`!clX^0uAj}QrjiT z>5f4@WEF!|tr<565H%{t0-|^!QZZ0=>Wy%hul|uV*4x}?yih>GVA4^^)~Uu|%0e0I z1@EeYnrh`T0X#%qGoefAjx$7bM`O_Drx=87nx-_(Q9N>}tJr=qsTK}8Df|tl!K%do-2lYA<2v8$=HJy12lK4uHj#SM>qcfq;d$rQ zKp9E@KLDRVV84k_A!h2ZCM$;bcv(VL2vNUb*A-o;NStaKgNE&x0|HBSqg`v64ZGHB zwOX}dz23H4-ClptW{_%|$SeU(f}#i;mF{tbLkM3|AxIz{uz&M`R4x80>^jzuZ+mUK zNjlwOqc&{RhqYF#S!df`r{3*02mQL!=SQP;bQr`yoPHI3hPSbo3wqtQNV_#?TX1CT zf`%V-;a?^(YbI%TTb)MRwyfsbdj0Fi_n=GsFC51i?=-xW-!15Kr)Rf29XR3k+g$?d zQ*&7B4LY4#w^<+Bup4c*hl92CSy(?qBZH#rqKvxdaWExibr7QNGYLJ1AWYTb$wNOZ zT36Z8JG{*;1p}iCq#JlJtnp-apT6B4EssFutFiSp2^agGE|k(8VB%S5XiP<^ICrl- z*TWTz_c(eG@8`+ck>h~A3>I}XEL60oVa2Gc;+{4xw9_etAfVMeHiCHX(-)^lWC&Qv znJZrr&YCUx%J9-tL+@t)2|v3Z5ey6~f109j?1wF4E3B9pu2f1qQUw8(C@0ZOP)c#K z0Ib(>WV5XD3j*xA9)L5%ZGpuQRHSnamcbB$s>ZGr+bcZBJHI;7l#~gRi(^|QeXI#c zjP$Htn%YboGQIOh@5bW}9hW6A4o6rk@cxYM16O1-+U%gDbx&nye)!zG%bBvXtkcBq z+0CBagTrK}RcrOZ0n>AAr)G8fPP5l@8wgXsXSr0!8u@0?3>sA*W%`; z#2(ukfL~`5d+bMIuh@>huPZp;8Of8nl4sYGJ_VO=aVTA|-ALT+kk0Av{^@uW3JG3d zyaEEs5XkY3E^~NTXSVl%dFDa~5WV!v{CN)54bk>6ZZL?*@ZNXuJSyLEJ9fpA@P|)ki8@#fq zW9p8tXk;52>zs9zJ%y67XJL$2UK?<?i0kbrc>+wYDB#2*-a(czdhrX zQJ3Z|`5p~djI_VN?jd%oG9B8__D3dsG|>&|a%bRK6WI2kFkn->U^qh&Nizf7r0KwR ztSMUHAJ`Cp_FRG@<|2xi85Ch1NmE6lOsYc?Z4~qgiz_I^KbkWCg-s}f*|fIgvdauM zVq(Lc%=${V?HpRdo+K}f3L3HH36CKQi{s?6?F9`~^^O7sSW)0lx)<)kTVIY66738e zVgr}6MH$Y2?sz_aH)WTPj@@e)Ee1a{*sal2zS?NwV23fE;iXLVwXLrH3N4mJ4tx&- zmnc{2^+f%Zj0DcY`1 zB$_q;Y{YA3o>Ib|eRxd+fX?9v8TIx6Oj?cBem(q0)o5gPIoU!u6s=amu_T*`2B~Z( zdli+2mfhtA-AK^PfqBIcExwVMP zGMba(8b!M-d?AycujP2GWi#;?qrP8<11$U(xRpNJQ}Pe%EiY0o@JpT`%LRVn0>8w; zlm_Gh%n>e;7&1q_(61+?Vg9n7#y;wX1xjrTV&+~}CH5%MT;km_yNv}aRc78u=>&^+ z{#Ag^M6|Z-EI6sKZegdT($dq#RnEXws;GpI7gc-rugAYAsv+5=YU2E)T4`oJ9M#B_ zpRx9Yf~RohNUQ(v|N5{0H#*hH$L9rY48##jwDEucf6=ikuML_h6-?7pGMi6f?r{Uj zw(H12v@DJWjoIKyaC{PYyECvygBFVq0QoM~8}(X4NqgpxT>G8|%obE-k5sI|3xAxC zt#6D%T)EH+Ah`hqo{4ki+`_JI^%xpL|qzO4N*io$~Li%EuTXAEvrT>P zYfhq&%E=d6GU#P?#%MrYO75gPO$KCSVu=$V;-xr|HNXc+weyMh6B=n}zT?jUoVdX5 z45r!&+G>sOsmYfA#lSzHCqdw5pmnh(pntC2Ym5HKbhF`0`f0Rwhh0f^QtdyZpQ+H| zbSji?ICicBu8=>t7n_+q)$FiuoPaW!df>0%AmoRhqy)X>(5lg`H7#nzqOvZaSL!>u zq=FINqRfYsfCCsa+AOP3hV;ESdO_MH`#eAf4Qmwb;feh1VXdHgzrxi=qIr&y8(2e$O;cM z^ru-3dA4ZnwQ=1zDy%F z@pHIc^$cwm@kM*7B|BrS{=gUNq|ZBAHa{fR=>@^WtMb7zbtF^QL99!xp6W(*qMCes zjrBAy(I5pm7|6%8N#-Lgc;0)m@XY~UyNJ^NJgP1TZzrV;kjZ<@rIuK?me|mtFfNfv zk$mRoL|J=_0Z11#wIWDhm-)y-D##e4Z1YoexZD<9B=$%PH-DT)AIUYXRt?`usT2hr zS?Cfrv0~@Qf6#h-DmgjBU#e|ol>TzzW)g3sr|XHXB)6LBm&t~s&t_~g6vDY3^paKPw?Sd-?nX#Ndg!H9!J5tkQa;Q+@rg&(nfVjg zl7?Y*@-}T3Q2dCKQ5Caj3)H1n=Ejt;CSX9Nu*Us%#f6ntO$+OmNL9k+1;r^Qrj)8p zIG2={rj4zXD+-lk;|LzngMx9Tn93=U^R-XLCK1e z(=)Rc*nXo=OAQoMmugL>l}=RNqnGOJ0n{0$YXfjPb^IvW1@=JhSf=HekwDcp<;Z1P zp0L<_%0`iYpN?8LS5@ogs%qVQCTcweKV&;Wt*zV)JOZ%0sHEE1ThdYx_Td;jty6bE zbK<;kBf&!f&odlC!tK*qd*a%xge0_IW!Bo*pE7Ih5?N~-8_rrA&$FeSvG!@@KYDgK zX=SK)b~f7h-0)1a!F<3zv8klA>Wp2*5+61mASrF;H_D{6GBwJiv}ICS)t@pc?F&sx z+t^f6+Qxd5(l%B|N}H-%U`ctNNog&9g2S6=Y;R)j?(NG@lj&;BGFk0flGUET4ia`` zc+5Q-f<*ibJKTWg_lAd8;$&@&a1Men_hG;;5^hsE+k6%jxu2g(n%h)@+61vs?6lNl%B&>;ozf;}@cP#b#$NdUa@a zW6ccT2Fe>g&$$5SMVgkC3|$WcrbX)1xIt;!H%?~84H-?Sqh{=TE*#%Pxar}@y1r&V z?v1%OM~fAWm4WidzBab*`4=6SZzziH-@!b9ufbuAexgcaW+Nrwb#%fEL=qT0yAzDW z0mjG)^Tgh|K1?_Y9i8b>WC=Zy`HxemC&IxJ#FsS-awnscPl6F5*|N> zVeV*HZ?riws6KEr{;2H!@dv;|!5<4hPR>uiecRppk!4JQ!?3dWThB=smL`b>5Whk? zLfzhv6k>vpaAH%FX;gZ~(TBZsEAV;IhRGU%4`(#dG0w0FrD-`b3(M$WV*q6d@hTBn z_V>k*_auZ3PQdB#7+B`e5LCDV(E-B^ejIEzv3B>O??pBLl0_cio~YETL*@7v+&NHW zL5VQW@n}1#I66EIj>waONmbPM-X#_VOJ_uwlQWjU^DSHMq+md3)j4ZIR-zA5B>kf~So_h)mC8__+-@Zr z;|qpAWe0{M+`P~|6*-3S;EQ`@lL;Gh);GYl0FBw11KK%(B?k0AQ!G2Wje={0-mA`T z_rk1qd#b6T@62XX_giew`N5iiKl8fqjI!D|rE?Svxg*DT=^ED70;x#9&#y|Gx@0W@^CLt zc+$hof3~Y9|I`F0hIP7tln#l+vD7T&JJ1?JL7&2^2P)}&6q|@54u_==w7l8U5g}8f zbVNL+BO*^t%ReQZ{!L-R7KY+;Znu89#`WcuRMP04siW3qMaZ?o>yIUD!pyVJuioYw zH1oFD^BXz#{6=ZdU$;H~9FT6rIcF#S;APwLlk6v4W-q~(-^kSJ$>F~MTRsPGfoYtH zMU>@47#68nUSX;P8^3430PQaUi^T25?n?zA9p9 zhgLNoU5tnE{&Z6Vn!o7=%w&D6&5KBNmXaEDYnH1Ilv+F%`zwia6FF~ejsT+Ln; zt>WTsGU+%5wctkCSboYV)CQoKBm1w)oXq-*hF{t%jP{B#)WSYlfeju2jN*e6x#T=v zdJ&bR;BjXtAO1X$$AB^u_8;6d=AN=jL=;+q|I92NvMMhh(Ilfn8lui?DrhL05O?880lXQP2u}F+L7Vv-^0dO%>E*Mxf`fw;3Q8E4?Hl-;)MtFysWe*L& zyB8y79jBC~OBLE#W%J8c}pXp}6a$Xq#)$&)ecUY38_2nx*Sx zEv}POG-nb6fPdgTAzztY@XA6E;K;uQp`;eom+ePM@*=M&v*6bHQJR^GJ~{lY@}rzY z&J!@6vTX2gxpWv*I`E1-y~A*0fjV^=ODpUbjExp3PZzw<=4-D-n7#6r@Fiy>Q zGQ&9fyIl2|XfmgGKn4~MiFmq=RrfFwii840t{tYY`i3qKtC-9E@0ixa~ z|0vtMgg*C>Sb>g*D?Wo~1RGRvH_XdpObXfJ$fKvb0|+gCfcxc9khhFUZO=kLINg zdBQs6jXxf^p4cK#3qZKd;p&3L7Ix;)8t%84O5yb)l_GCPME6;KzPR>DFNW6i`U+P- zc~Pty-|s0nR_0UI|1<6}f9E356TqL7`Rvk*_fC8=OrRP2P_)oiH@I4c4sPbdPcAyu zQ(AyZMOXnzFCtRo8|Upg-4jv2uOg3Ri&weT(X3b}&Qc zp7+g9u?UxUKDQXYkhBVeMf9QJ3(Xbb3yLAE3|}Z_Fy0J&LDOK*n=RoBGBry0LJ42k zdia8_l3E(Ru&O6!2>?UZn8GHS#Q_XO+gyW&-WC8uD+j>PDgg}Z2QXkt;wjqL@Pvmq zn=px(Z(I;-Of7`&cnszvm<8|$f~gQZ27xhjN91MW8Qol%A5YkNK{6@`2z6uQN`liI@hR1S|NyLAyUjW&r2~_Lk!; z@bcOatoFggwdrj*5ma(T_yjwC!BZSrzy?akgF3;VBBmooua!X+=)J&i@mr@NxE#h= ztTUg&#xNJ5+|k)h`cM-N(GO%iAX9LzVa}+8Lr8ijd*UQVC@T@5yJsJ4MG4=lTtD<5 zUG9AM_J3S_7kx|)yC}Yu2*D^hOr$oM&*ZloU(^yV6dbp>wuxW(hYx&+Qhexw!FLLt zm+vtmmTw;s>Y%d^FPVP>!fmbTk995;f zt7?ZX^dTX-ka#)Z>R#+X^v947W3&V1RPPMbQ?zvmva+bS>Yfj>d z6S?$~&L{MFP2KvYuh?#c?-!DizLp(HM%(#XW@G!4Cxq*h#>PN>OAo3D*e|W@;t2Hw zN;1}`%bmXgT$v#y(+$W-agA00)0ya#JmiI)iql5!m7<9S-BPxb*)L9fot}mBr083` znf%@*+miaX0Vz>UY}RuLrI|5xizg(5B(aJa)Y?Ffh^%Z55)kygf2S@}W{8mglqgUw z6ewN7p9chVbBQk6oz4qEfJ*xFslh9P zFNQqY3{YMZ`Nf+pf$}lE31Gib3dl^K$~nm;O_F7;0ECm8)$!cMSBNufIs@CkdQAzj zZjnj)jXy$x8BcpTHo4>_U9A(rWl|cFJ2g>UzsSN<-SHavZ0iVwr zY1l-oIDEcnn`_Y9+XA0&=fLONC47E!@OeDf(6cp%bDVh8Jo>Ux@=1>56=g=;T9kY{ za~Vhuf16SA!;uBBIYg`bHp{&NlZAluI9A?bb^t7iz<4;6B%$*h^zJ3W?mqa!F`fL% z!T#i1FbqS3pXAbPz?aS`tRDT7LnPx`@aj}Yz}LVW!n<>5R8A*9FhAnA&wi18zr09@HVY$(K`~|PvJ+Sh zB!j9^w4TV(D?$cbdfxy>kRWF`&1r(>f`9bkeHxTq98ODs%ryxg4~nMO^xjjLtyN)0 z$}nc>i4TP+C0LaNbXGB(LTFV)*olEIz^ul?T7^B8#XY;HAuv?YSAG1Y&|vZYX5%o` zJ%$cS5~Yx{r$%4R-aauJOTA^xzjWps*gkT7!h5Ga+ZDIwMgulYH6aS_I;e zx+I2u=_z2Hq8k*E_ooqoOp!A^RI0N z3#RGcPQ*S?pB0C^nZ63gXXH)Bdr{D^xoYq)?umd#EJm(DrNN`=aLsaP;wCAE>DsjGOjmq3GMjWukd zT^tQow9PeW?rlMXb#l;Poe~YUel*wzFh^g3#hGSkxw>_&tG8cIuRwi!0fL0DrZ5pN z8xodeOI}gt#;t{fbuw3jP%sE4&(<#H3W+BE7Q>h=*h$f!-@fX--h9kfMuq=?6 z-ZAkw0xyBVZFFxO--CF#b|T53!=B0+UD1ah25O4p%_uTMj;$}`YG5Ij$o>+ch*DqL z=is6A3zqd&L=B5Uq;M46OKe*K345eSY?&fRJceO4qpFgY$zjsIhb>(0{OW!2{JSu~ zR0<;Ymj0C>l#U-{tCt# zLB&s(J7J@YAXxBn2XO+Tdk7nd_4^$vn#1%2qX(Xlnt312-md;(-f+12`uLm(0SS^ zR<}YWhC$RYulP{_qre>S;xTl50=REKBdqKb(46|SRVvm^D%Qyoid8~_9bH2sOX85j z`g_knUl+^a91iU%sCg;C;lPJuFm86C$Cn-how~tr{`>FuhqLLNsP?|q^csP1Ky~Os zY2H})!ibj9JSKsld6Uhiv*3i~(6|U$7U`V3IT0lq&F)pz+ z>&Dh3y%AuEe`st?XGLs{VhAf^Yl=B4HiNCvbQJMsOKgoyjS^c^Vr#Y@TcfL_Hh`@u z>fc)eO_Mdcu!&A_G)>Vq*PyGn1x?e9Ibo2cz&9+~#>gGJGs7CV zaJst32Y}4+q>eZ|F)?i&I(5H$3)Xx>VrBM?*JR}1(OkzyU;%FsYV5z~)zIqCI*gH| z;Gv5h`bdW0r}b>|gCXSd?(S|MEV1C_prSZE2<`*R0UJLo4`YV{gX*sJL4N+u=LdW$lJD$> zj3&T1UzqF6LaAf_j1oJu-$QT{vww|657|#a7hY{*joH5(-vD5TVMF0Bu_wUB9(!=( z{sXzrVZYvi^>>7hyOonJFN$*&dGM+0DI>!ox{E3OnX8xrRVp7z0HCjP8bp8`rY{)j_pK_X~VuG`P*1-GZ9AQ1lTHIt)nq3W@*`)&98MQy!?k0%>SJ3PM)GEOkKgbeju{FX)T9UJ9)gxq)NQi7=iOoA`Yco`jQB6hW!_ zL3mm->VJyWhyRYRf|)g)0WRv(r_Z|!+y2bk-b=Tx0)z13!hqc2up;5URFeU&Vb$Ym zvTlmJP%-tax?nmIan4ZSj|sdUr7$ZooJo}P6zqN}Dkb22SZ4224PElNtzE(&jRtA4 zM4MTNmB^3H^B)2|aE{R@{Oim{5Kz*$4MpC1j#R+Du{hC)mQbm6+^;RhIK>06gHSAS{;5c53;uD z^==v1jEwD7FOrg`UC#$%E9}}^*IJ&J`a-NJ<*Fv}Df`rZhQ5tKanj3Hx*e77jWx1h z5zOqBN<}ZRa?5k5vjiItxOPwS*Y3%pwR^JK+C5pkc2Bl(?bchh7p>RYEAxKZ<`@L)kV7FI703R_P;S&u$SVq?>OtQo} zT>=5A*By?l7AklhmmBrBEp)ksu0IatQ+m{P_W+c)Ss-B2u?kMbhXw+6R|EnohOjaa zu$a$vGeAI1pEqx|1Om#`D1m?_5OC{(fVxU*BS65#Uj8M3e_5jmo9Gq?{uOO=4Z3+- zfPcLl;9su<{%rvG$Aj{S>@Kh&eBqfClWfQ<%G|fLz`tImeoqenMP^c@m_CfC5r(pw zq1cE7iI(A5_X5m^2RDYfgAri(D0w-1BkX7x8!}3ZEfQU?8WT(OjtVtLBGTl<004yU z^AKDsW8}bYe-4fWBosJO-0^&Dcymm=5Yh@mfzdu-U@@So5n<#ED}b)l{wBz;i+Uj{!z;psq{vM6C9t*$Q79rcjV!{itqh9b(#4DmhS*p z_2W4Ys<1FpNmTxA{^G$Ck1^TusX_iHik~lYl@;~A9pUDfDm!vaugCeYEGr#x58zUK zM!CvP#;}E)t<_7|>M8R!@OS;vODM=udI_JNmrx-Hc{vNJ#gDBo-0PLJD!_^Qp}k(c z6}?`HA*}56DrRuljMq!k;KQ3Oy9t%9ND{{YJzzle;&Lji>L>Ezj24?sic(`bX_l;O?nE}fx z!=q}{kla%Y{OZVciszb12FqAm-Wl-$wC@J&8l=G$2kX>1j`uj~`?}n1EOtl449rMQ zpALno7>wj5TC>A97<0qNyJ%hK0ljMR2->d<>)NkK-Qk!2buQu3ga*HQJkSWA!plR zpSp5~PF2m(G}1*ucbTu$}qVDnhjK1t-@th z!GL^JxW|E2%^y}+fA*d^TB6v>y*O&r^IE^-aiSh}fzC?MSFXV23S6$hkCj}oT!G6K zcuQbz`rD;-LE+RJ^C%=h^||P=732<1C^3oWoQoLrHAGyQ*)K6 zxft`6aXAVg?yAe@e>*vQ$kbe7oh}1%Y%w%3>E;C&=R-pi`zt~d6+>7Vnpn&OycuYs zrU#xkTS60MYLw8#5}LU6&_rD&wMl5=QV578P>NY&4V&l}M=2Ij*tdl| zgt@d}86v>@2>e5gnlbXP!E$_Y28+{>fV=rimHz7pafdg^nD7=FJ;M}DRrFIIk0u5a zHUumlfmhl=@v#7dH@5B(Q%F;08R0!q`pH|%AyPyU^mpI-E_k(RS6F*B=IC}<@Y-0? ztsu&}<@k26KMolQ1#5yZrQn8)Mh9b9rbgs|aI4_~bk2hMPHlFBx2j=Aecp$IDH;2> zq-MeBXaXwCRQ6Y#?n6<#Hdz%F$$y;D@fjClHr4@sq&Nn|l;!~t(U(Gak<-b0@GscoQ1MLWTzg0I(X&;iEhhgt;lX-&50|LsVe|=381|A7&(kA z0f$^>XmU1(oRKMduV@0{gDJSE0?f8_8VD(yKoWHp^&*|&09h`beE=%U{K7n&$y@{p zNRTE{_mN;`)Hxh!|1)c3vLj|1hbCnwR!89(L2(E7zVg6(V`=84ySZ)0(^T%@9 zRV$z0Lgl)0M!&_n^=wJqT4Z2K)v6wsA{*gLQ>~AwE(Izm2K}L6Aiir3l_T5xFsP3k z4ClZ9et$Td!pTD3JXIyZzVvdYNPeW6q38j;z3^w&C^bjWekJ};%$M$5cg&ehwmu$h|C zS$g5vA2UPdWH#Jh?2nnP^^*kH>Of^Alm}zzVqzY&z0sg#b1pK7%i$sghUr`QI{>4$ zePe*Dn=i=04fCFB5aT5&#^oP9>RjV zP+1UGBpgCC5(}aR^Mx0VDewqzQ3m+3u4!})J_FQeJGMrE)FtR}WbML(L}s29!9c%| zJ0nt4#;t3K_wQg#LX$uK|`1YYTtmFJz&ru7UT|QVTAkw9Ch!{fz5FGW*qnelz;{?Fph{punW)Nn7#{a zgnuC;;orbY_!ltKF7+N^ZR(Dku>d?h5!nq~So{$U_O`L;FNZYz3;$ZCR~LN7f}Zgr z=TvzjeM5pq*eOnyqz72`MT2|L0vWJL6kIHNjc(nYU%fA$f9FL{!SzPN#Ju$av^Yqu z(TrTImE;BX*r=p`yeLjfC6C85S&5N=RKQz&_YNon3sV%CkJ&OKxj_@J02bzFDiKnscGR}1bIs;nUPF-en! z9}+qYIx3`~QIIck1hZ3iO{CyEdvCG&Jh5F@ixo$EZOtrdb;V4W6d5y`OkR;PC zPwS_*X+!~9gZ6*}WvE`4Bvn{{Zv#6`4Y}U%#xp7S&qer)D}VNv8ya1pV3`407HmN|TtKwA_iAfnwWw?CE#8++O z%TlBCH8`W(pp`a$)*GB0%*PxB!W^^s@q zd0^^6+kiveIr5x0ECW&o$o)O0Ot(fK!1sh!>9s=Z&3bk=>vp$ZLhA)NN@#rvt$(($ zHCMQU@axC-4;h72?1YNk*MQQGO(ooMJtUdJ9bN%C(Yk9PRC!b50YbH=z-|chdhnYX z=BhU}RHIn^rba<1+=gyy=)w_s!{tp4nHuFyjq;|(_TSXdR#Y3fsj&(ubD1j~X6|JR zbBQY)gEo8V~Rh~t7BW9!Br;wdtwb-I5A-}^MDNJBYI1MCy-w?lIchB z%`+dT#HAhi8F--f(G9+cvXLOKss7iy^9ZlT*U&!o@KP3z6-!<gTip)`L3ifhB<%M3-^(VE?%$}LdnYnUMN{L$d z=gC7?OOY!@?$JbU^3+7`$z(rGKyw+|B!R+^As47Zmw|vJ?~SFuun=Ss@4(s-7a7F@ z0*49MJYbpk{++tad;&X5x)dGHxj6uvyfA;xEB`is=L6hyeNCo}Tt4+ilrapSeGJ1b z30x>8Hhrj|g{Ijmkrjd*C99B@ExtXtQo7Z(FE!w__aCB?PF;7sU>g(kpJJ~Nm@Gr*7nG!g*| zm~h~5VH08g@GHZqKQ=sohMk-l7CQ!$VBa{I86-fU7&tbuR|aM*rLnL4q43X#-KrWO zC3xzAFV4^v4~9q#Um>CiR_QuB99iIaLw70n_mi(SAvE5hg{e@j`ACk{2j6M5S9Ik| z!5tCfCb{bn{f1EpFzO+!`@ebt#-%tpTiu(Via6L7f&6}tx`VxuLRz4&SN-mCC#)T9 zMdV0=mlXUj)F^S7=U7Jcq0dMWOVDgB3MP0=hSZZ2n|BYdnvN1InUw_)OTFfm`J@yH zJ;+0aBqD|E?CS0;0!c{c!T0_eEeeRN}95u-~H(?U3dB>zx!#W$WY zPHySw_F6P%-CkFC<}Ka!#7zDKbHU}2PMi?R%!y-#=@9wzC8qDyf*?*$W1iNL&JIQb zjW3w-=(_j9xf0>i*fOXJJQ(wm=jbTt>;-&3QKwcOHBs)cqA3?oxfDsrt4 zi_+Sko+LHlu^<;JkwH+A=eQPXm_6el{C5Pq8NnGVb&`pW3zwdX)^SmeKA(!PtBBhI ztSPf;bur_q!m}jT>+JPg!I;&kyU;op1Bqa%kTt9f9v!7DxEB73)=D;bY!@1yc{eP}h%KYL;+n z#BY`Ot?kEeX)CG?;I~%v?Jfi1YUYBinoB^q3b(oz6}~kPu2vp|t5rg{9umS8+BaV~ zlB<;=donw2FOsX3y)q;Tu>D9bcSsWidh_uBEcxK4cYJ$}94B(V!rBIY4*}1DUlf&v zz7LyJf<3V02fhui0(e8E#sX(XxW3KV`o_Gi)V+tBHehrSZ+<-r>5?Q>+Ohra*P{%NH22vl^E$)_5VrB^Fztl|Ap-a&xFJ&x z>eSHah=eCsayP@1_cEw1e^kXlBI|*MWDbkDIQe|VkZ0`87NX8rkqCc88%y%%hF}(P z&$O6Ehp|HZWvVnyfi5NTN)-=>q{!wK1r`BDS&$Be3syWciX5H8NW0UMOtv`K&qAfS zMy%V?TC{{6EzHQElYwHg}jRlXGl50{;*Jna777GmI%U? z4Oa~JS?SGXCTLN$Q_DjQ=0F7%WLcO3shQBb3nR4$h8isaGt&l>}mEK$AO}Y z$d^E6VnIUXMEBMHD<0R36imuzfieZSFGfb&nB#mL0nzwN4n*72Np`6xx&0$>yq zn9$mkFqGwDY^Wr)@o8O@#4iQoRB#2ubVf=sf#QrBW0=koUWqBtFwNo4g79(|V$TJ( zpt{myJ+o$WwCn90-CAy$oiF@T!YfmMN_ge!pf0A+3P8Hv%}1lfX{xI0>J|O9uH;#3 z>s7d-6~V!Ub=HE0D}=O;XFQZY@X9!zH*_EWwrA53bZ!R2v0X zt_#7j4BoPpi;Qe7fwwH&>RPn=*5EDMd3eiqiML!o-ZDUeoKUQ0B?)1zonYSBxY9wiR?4-nC{+*d)3iaKR-8V38;?0FJcb8^Gog zMi72jhS{!xb8!yhLBV(v8i!#2(mNXwM3JCxlclO-)(-FyQZoh(U13e9);$0|11tv4 z%L(QNLgfm|0!N=t>7-hKctA+_PQTwDE(J-p zkAVuGkK|;_MGT&~0OhHckwgfi4N6M9pQXI4lx^>6WY|}nU{)bH z%3xMyFsqk{x4?bELqb!ceaYG&Mk@xiQaaJ+!OR~Y10up5Wfn-L1+HR07w+xV z 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 Date: Sun, 6 Oct 2024 14:53:45 +0300 Subject: [PATCH 18/27] update --- ... Mail_redirect_via_ExO_transport_rule.yaml | 27 +- ...repoint_file_transfer_above_threshold.yaml | 40 +- ...file_transfer_folders_above_threshold.yaml | 81 +- .../Global Secure Access/Package/3.1.0.zip | Bin 48462 -> 0 bytes .../Package/createUiDefinition.json | 729 --- .../Package/mainTemplate.json | 5028 ----------------- .../Package/testParameters.json | 40 - ...repoint_file_transfer_above_threshold.yaml | 2 +- 8 files changed, 75 insertions(+), 5872 deletions(-) delete mode 100644 Solutions/Global Secure Access/Package/3.1.0.zip delete mode 100644 Solutions/Global Secure Access/Package/createUiDefinition.json delete mode 100644 Solutions/Global Secure Access/Package/mainTemplate.json delete mode 100644 Solutions/Global Secure Access/Package/testParameters.json diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml index d6a9ebb224..256c1fac3d 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - Mail_redirect_via_ExO_transport_rule.yaml @@ -6,12 +6,12 @@ description: | severity: Medium status: Available requiredDataConnectors: - - connectorId: AzureActiveDirectory - dataTypes: - - EnrichedMicrosoft365AuditLogs - connectorId: Office365 dataTypes: - OfficeActivity (Exchange) + - connectorId: AzureActiveDirectory + dataTypes: + - EnrichedMicrosoft365AuditLogs queryFrequency: 1h queryPeriod: 1h triggerOperator: gt @@ -23,6 +23,7 @@ relevantTechniques: - T1114 - T1020 query: | + // OfficeActivity Query let officeActivityQuery = OfficeActivity | where OfficeWorkload == "Exchange" | where Operation in~ ("New-TransportRule", "Set-TransportRule") @@ -40,30 +41,30 @@ query: | | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIP)[0] | extend From = ParsedParameters.From | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters - | extend AccountName = tostring(split(UserId, "@")[0]), + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - + // EnrichedMicrosoft365AuditLogs Query let enrichedLogsQuery = EnrichedMicrosoft365AuditLogs | where Workload == "Exchange" | where Operation in~ ("New-TransportRule", "Set-TransportRule") - | mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on ( + | extend AdditionalProps = parse_json(AdditionalProperties) + | mv-apply DynamicParameters = todynamic(AdditionalProps.Parameters) on ( summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value)) ) | extend RuleName = case( - Operation =~ "Set-TransportRule", ObjectId, // Assuming ObjectId maps to OfficeObjectId + Operation =~ "Set-TransportRule", ObjectId, Operation =~ "New-TransportRule", ParsedParameters.Name, "Unknown" ) - | mv-expand ExpandedParameters = todynamic(tostring(AdditionalProperties.Parameters)) + | mv-expand ExpandedParameters = todynamic(AdditionalProps.Parameters) | where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value) | extend RedirectTo = ExpandedParameters.Value | extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P\d+))?', dynamic(["IPAddress", "Port"]), ClientIp)[0] | extend From = ParsedParameters.From - | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) - | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters = tostring(AdditionalProperties.Parameters), UserAgent - | extend AccountName = tostring(split(UserId, "@")[0]), + | extend UserAgent = tostring(AdditionalProps.UserAgent) + | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters = tostring(AdditionalProps.Parameters), UserAgent + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // Combine both queries union isfuzzy=true officeActivityQuery, enrichedLogsQuery | summarize arg_min(TimeGenerated, *) by RuleName, RedirectTo @@ -83,4 +84,4 @@ entityMappings: - identifier: Address columnName: IPAddress version: 2.1.4 -kind: Scheduled +kind: Scheduled \ No newline at end of file diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml index 4d072b9dde..56ca53b9f8 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml @@ -1,7 +1,7 @@ id: 30375d00-68cc-4f95-b89a-68064d566358 name: GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold description: | - 'Identifies Office365 Sharepoint File Transfers above a certain threshold in a 15-minute time period. + 'Identifies Office365 SharePoint file transfers above a certain threshold in a 15-minute time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur.' severity: Medium status: Available @@ -22,39 +22,37 @@ relevantTechniques: - T1020 query: | let threshold = 5000; - // EnrichedMicrosoft365AuditLogs Query let EnrichedEvents = EnrichedMicrosoft365AuditLogs - | where Workload has_any("SharePoint", "OneDrive") + | where Workload has_any("SharePoint", "OneDrive") | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") | extend ClientIP = ClientIp - | summarize count_distinct_ObjectId = dcount(ObjectId), fileslist = make_set(ObjectId, 10000) + | summarize TransferCount = dcount(ObjectId), fileslist = make_set(ObjectId, 10000) by UserId, ClientIP, bin(TimeGenerated, 15m) - | where count_distinct_ObjectId >= threshold - | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), + | where TransferCount >= threshold + | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), + strcat("SeeFilesListField_", tostring(hash(tostring(fileslist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // OfficeActivity Query let OfficeEvents = OfficeActivity - | where EventSource == "SharePoint" - | where OfficeWorkload has_any("SharePoint", "OneDrive") + | where EventSource == "SharePoint" + | where OfficeWorkload has_any("SharePoint", "OneDrive") | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | summarize count_distinct_OfficeObjectId = dcount(OfficeObjectId), fileslist = make_set(OfficeObjectId, 10000) + | summarize TransferCount = dcount(OfficeObjectId), fileslist = make_set(OfficeObjectId, 10000) by UserId, ClientIP, bin(TimeGenerated, 15m) - | where count_distinct_OfficeObjectId >= threshold - | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), + | where TransferCount >= threshold + | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), + strcat("SeeFilesListField_", tostring(hash(tostring(fileslist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // Combine Office and Enriched Logs let CombinedEvents = OfficeEvents | union EnrichedEvents | summarize arg_min(TimeGenerated, *) by UserId, ClientIP; - // Final Output CombinedEvents - | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist + | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, TransferCount, fileslist | order by TimeGenerated desc entityMappings: - entityType: Account @@ -74,7 +72,7 @@ entityMappings: - identifier: Name columnName: FileSample customDetails: - TransferCount: count_distinct_OfficeObjectId + TransferCount: TransferCount FilesList: fileslist incidentConfiguration: createIncident: true @@ -84,8 +82,6 @@ incidentConfiguration: lookbackDuration: PT5H matchingMethod: Selected groupByEntities: - - Account - groupByAlertDetails: [] - groupByCustomDetails: [] + - Account version: 1.0.5 -kind: Scheduled +kind: Scheduled \ No newline at end of file diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml index 500b037a4b..de4935de26 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_folders_above_threshold.yaml @@ -1,7 +1,7 @@ id: abd6976d-8f71-4851-98c4-4d086201319c name: GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold description: | - 'Identifies Office365 Sharepoint File Transfers with a distinct folder count above a certain threshold in a 15-minute time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur.' + 'Identifies Office365 SharePoint file transfers with a distinct folder count above a certain threshold in a 15-minute time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur.' severity: Medium status: Available requiredDataConnectors: @@ -20,39 +20,44 @@ tactics: relevantTechniques: - T1020 query: | - let threshold = 5000; - // EnrichedMicrosoft365AuditLogs Query - let EnrichedEvents = EnrichedMicrosoft365AuditLogs - | where Workload has_any("SharePoint", "OneDrive") - | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | extend UserAgent = tostring(AdditionalProperties["UserAgent"]) - | summarize count_distinct_ObjectId = dcount(ObjectId), - fileslist = make_set(ObjectId, 10000), - any_UserAgent = any(UserAgent) - by UserId, ClientIP = ClientIp, bin(TimeGenerated, 15m) - | where count_distinct_ObjectId >= threshold - | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // OfficeActivity Query - let OfficeEvents = OfficeActivity - | where EventSource == "SharePoint" - | where OfficeWorkload has_any("SharePoint", "OneDrive") - | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") - | summarize count_distinct_OfficeObjectId = dcount(OfficeObjectId), - fileslist = make_set(OfficeObjectId, 10000), - any_UserAgent = any(UserAgent) - by UserId, ClientIP, bin(TimeGenerated, 15m) - | where count_distinct_OfficeObjectId >= threshold - | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField", "_", tostring(hash(tostring(fileslist))))) - | extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1]); - // Combine Office and Enriched Logs - let CombinedEvents = OfficeEvents - | union EnrichedEvents - | summarize arg_min(TimeGenerated, *) by UserId, ClientIP, any_UserAgent; - // Final Output - CombinedEvents - | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, UserAgent = any_UserAgent, count_distinct_ObjectId = coalesce(count_distinct_ObjectId, count_distinct_OfficeObjectId), fileslist - | order by TimeGenerated desc + let threshold = 5000; + // EnrichedMicrosoft365AuditLogs Query + let EnrichedEvents = EnrichedMicrosoft365AuditLogs + | where Workload has_any("SharePoint", "OneDrive") + | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | extend UserAgent = tostring(AdditionalProperties["UserAgent"]) + | summarize count_distinct_ObjectId = dcount(ObjectId), + fileslist = make_set(ObjectId, 10000), + any_UserAgent = any(UserAgent) + by UserId, ClientIP = ClientIp, bin(TimeGenerated, 15m) + | where count_distinct_ObjectId >= threshold + | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), + strcat("SeeFilesListField_", tostring(hash(tostring(fileslist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), + AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // OfficeActivity Query + let OfficeEvents = OfficeActivity + | where EventSource == "SharePoint" + | where OfficeWorkload has_any("SharePoint", "OneDrive") + | where Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded") + | summarize count_distinct_ObjectId = dcount(OfficeObjectId), + fileslist = make_set(OfficeObjectId, 10000), + any_UserAgent = any(UserAgent) + by UserId, ClientIP, bin(TimeGenerated, 15m) + | where count_distinct_ObjectId >= threshold + | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), + strcat("SeeFilesListField_", tostring(hash(tostring(fileslist))))) + | extend AccountName = tostring(split(UserId, "@")[0]), + AccountUPNSuffix = tostring(split(UserId, "@")[1]); + // Combine Office and Enriched Logs + let CombinedEvents = OfficeEvents + | union EnrichedEvents + | summarize arg_min(TimeGenerated, *) by UserId, ClientIP, any_UserAgent; + // Final Output + CombinedEvents + | extend TransferCount = count_distinct_ObjectId + | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, UserAgent = any_UserAgent, TransferCount, fileslist + | order by TimeGenerated desc entityMappings: - entityType: Account fieldMappings: @@ -71,7 +76,7 @@ entityMappings: - identifier: Name columnName: FileSample customDetails: - TransferCount: count_distinct_OfficeObjectId + TransferCount: TransferCount FilesList: fileslist incidentConfiguration: createIncident: true @@ -81,8 +86,6 @@ incidentConfiguration: lookbackDuration: PT5H matchingMethod: Selected groupByEntities: - - Account - groupByAlertDetails: [] - groupByCustomDetails: [] -version: 2.0.7 + - Account +version: 2.0.8 kind: Scheduled \ No newline at end of file diff --git a/Solutions/Global Secure Access/Package/3.1.0.zip b/Solutions/Global Secure Access/Package/3.1.0.zip deleted file mode 100644 index b4bf1cfe08054024f9166a0bb0d954d371b7a4d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48462 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 z 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\n\n**Note:** Please refer to the following before installing the solution: \n\nā€¢ Review the solution [Release Notes](https://github.com/Azure/Azure-Sentinel/tree/master/Solutions/Global%20Secure%20Access/ReleaseNotes.md)\n\n ā€¢ There may be [known issues](https://aka.ms/sentinelsolutionsknownissues) pertaining to this Solution, please refer to them before installing.\n\n[Global Secure Access](https://aka.ms/GlobalSecureAccess) is a [domain solution](https://learn.microsoft.com/en-us/azure/sentinel/sentinel-solutions-catalog#domain-solutions) and does not include any data connectors. The content in this solution requires one of the product solutions below.\n\n**Prerequisite:**\n\nInstall one or more of the listed solutions to unlock the value provided by this solution.\n1. Microsoft Entra ID \n\n**Underlying Microsoft Technologies used:**\n\nThis solution depends on the following technologies, and some of these dependencies may either be in Preview state or might result in additional ingestion or operational costs:\n1. Product solutions as described above\n\n\n**Workbooks:** 2, **Analytic Rules:** 19, **Hunting Queries:** 21\n\n[Learn more about Microsoft Sentinel](https://aka.ms/azuresentinel) | [Learn more about Solutions](https://aka.ms/azuresentinelsolutionsdoc)", - "subscription": { - "resourceProviders": [ - "Microsoft.OperationsManagement/solutions", - "Microsoft.OperationalInsights/workspaces/providers/alertRules", - "Microsoft.Insights/workbooks", - "Microsoft.Logic/workflows" - ] - }, - "location": { - "metadata": { - "hidden": "Hiding location, we get it from the log analytics workspace" - }, - "visible": false - }, - "resourceGroup": { - "allowExisting": true - } - } - }, - "basics": [ - { - "name": "getLAWorkspace", - "type": "Microsoft.Solutions.ArmApiControl", - "toolTip": "This filters by workspaces that exist in the Resource Group selected", - "condition": "[greater(length(resourceGroup().name),0)]", - "request": { - "method": "GET", - "path": "[concat(subscription().id,'/providers/Microsoft.OperationalInsights/workspaces?api-version=2020-08-01')]" - } - }, - { - "name": "workspace", - "type": "Microsoft.Common.DropDown", - "label": "Workspace", - "placeholder": "Select a workspace", - "toolTip": "This dropdown will list only workspace that exists in the Resource Group selected", - "constraints": { - "allowedValues": "[map(filter(basics('getLAWorkspace').value, (filter) => contains(toLower(filter.id), toLower(resourceGroup().name))), (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]", - "required": true - }, - "visible": true - } - ], - "steps": [ - { - "name": "workbooks", - "label": "Workbooks", - "subLabel": { - "preValidation": "Configure the workbooks", - "postValidation": "Done" - }, - "bladeTitle": "Workbooks", - "elements": [ - { - "name": "workbooks-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This Microsoft Sentinel Solution installs workbooks. Workbooks provide a flexible canvas for data monitoring, analysis, and the creation of rich visual reports within the Azure portal. They allow you to tap into one or many data sources from Microsoft Sentinel and combine them into unified interactive experiences." - } - }, - { - "name": "workbooks-link", - "type": "Microsoft.Common.TextBlock", - "options": { - "link": { - "label": "Learn more", - "uri": "https://docs.microsoft.com/azure/sentinel/tutorial-monitor-your-data" - } - } - }, - { - "name": "workbook1", - "type": "Microsoft.Common.Section", - "label": "Microsoft Global Secure Access Enriched M365 Logs", - "elements": [ - { - "name": "workbook1-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This Workbook provides a detailed view of Microsoft 365 log data, enriched with contextual information to enhance visibility into user activities and potential security threats." - } - } - ] - }, - { - "name": "workbook2", - "type": "Microsoft.Common.Section", - "label": "Microsoft Global Secure Access Traffic Logs", - "elements": [ - { - "name": "workbook2-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This workbook provides an overview of all traffic logs within your network, offering insights into data transfer, anomalies, and potential threats." - } - } - ] - } - ] - }, - { - "name": "analytics", - "label": "Analytics", - "subLabel": { - "preValidation": "Configure the analytics", - "postValidation": "Done" - }, - "bladeTitle": "Analytics", - "elements": [ - { - "name": "analytics-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This solution installs the following analytic rule templates. After installing the solution, create and enable analytic rules in the Manage solution view." - } - }, - { - "name": "analytics-link", - "type": "Microsoft.Common.TextBlock", - "options": { - "link": { - "label": "Learn more", - "uri": "https://docs.microsoft.com/azure/sentinel/tutorial-detect-threats-custom?WT.mc_id=Portal-Microsoft_Azure_CreateUIDef" - } - } - }, - { - "name": "analytic1", - "type": "Microsoft.Common.Section", - "label": "Detect Connections Outside Operational Hours", - "elements": [ - { - "name": "analytic1-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This query identifies connections that occur outside of the defined operational hours. It helps in monitoring and flagging any unusual activity that may occur during non-business hours, indicating potential security concerns or policy violations." - } - } - ] - }, - { - "name": "analytic2", - "type": "Microsoft.Common.Section", - "label": "Detect IP Address Changes and Overlapping Sessions", - "elements": [ - { - "name": "analytic2-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This query identifies network sessions based on DeviceId and UserPrincipalName, then checks for changed IP addresses and overlapping session times." - } - } - ] - }, - { - "name": "analytic3", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Exchange AuditLog Disabled", - "elements": [ - { - "name": "analytic3-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Identifies when the Exchange audit logging has been disabled, which may indicate an adversary attempt to evade detection or bypass other defenses." - } - } - ] - }, - { - "name": "analytic4", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Accessed files shared by temporary external user", - "elements": [ - { - "name": "analytic4-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This detection identifies when an external user is added to a Team or Teams chat and shares a file which is accessed by many users (>10) and the users is removed within short period of time. This might be an indicator of suspicious activity." - } - } - ] - }, - { - "name": "analytic5", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - External User Added and Removed in Short Timeframe", - "elements": [ - { - "name": "analytic5-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This detection flags the occurrences of external user accounts that are added to a Team and then removed within one hour." - } - } - ] - }, - { - "name": "analytic6", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule", - "elements": [ - { - "name": "analytic6-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Identifies when an Exchange Online transport rule is configured to forward emails.\nThis could indicate an adversary mailbox configured to collect mail from multiple user accounts." - } - } - ] - }, - { - "name": "analytic7", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Malicious Inbox Rule", - "elements": [ - { - "name": "analytic7-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Often times after the initial compromise the attackers create inbox rules to delete emails that contain certain keywords.\nThis is done so as to limit ability to warn compromised users that they've been compromised. Below is a sample query that tries to detect this.\nReference: https://www.reddit.com/r/sysadmin/comments/7kyp0a/recent_phishing_attempts_my_experience_and_what/" - } - } - ] - }, - { - "name": "analytic8", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Multiple Teams deleted by a single user", - "elements": [ - { - "name": "analytic8-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This detection flags the occurrences of deleting multiple teams within a day.\nThis data is a part of Office 365 Connector in Microsoft Sentinel." - } - } - ] - }, - { - "name": "analytic9", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination", - "elements": [ - { - "name": "analytic9-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Identifies when multiple (more than one) users' mailboxes are configured to forward to the same destination. \nThis could be an attacker-controlled destination mailbox configured to collect mail from multiple compromised user accounts." - } - } - ] - }, - { - "name": "analytic10", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Office Policy Tampering", - "elements": [ - { - "name": "analytic10-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Identifies if any tampering is done to either audit log, ATP Safelink, SafeAttachment, AntiPhish, or Dlp policy. \nAn adversary may use this technique to evade detection or avoid other policy-based defenses.\nReferences: https://docs.microsoft.com/powershell/module/exchange/advanced-threat-protection/remove-antiphishrule?view=exchange-ps." - } - } - ] - }, - { - "name": "analytic11", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - New Executable via Office FileUploaded Operation", - "elements": [ - { - "name": "analytic11-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Identifies when executable file types are uploaded to Office services such as SharePoint and OneDrive.\nList currently includes exe, inf, gzip, cmd, bat file extensions.\nAdditionally, identifies when a given user is uploading these files to another user's workspace.\nThis may be an indication of a staging location for malware or other malicious activity." - } - } - ] - }, - { - "name": "analytic12", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Rare and Potentially High-Risk Office Operations", - "elements": [ - { - "name": "analytic12-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Identifies Office operations that are typically rare and can provide capabilities useful to attackers." - } - } - ] - }, - { - "name": "analytic13", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - SharePoint File Operation via Previously Unseen IPs", - "elements": [ - { - "name": "analytic13-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Identifies anomalies using user behavior by setting a threshold for significant changes in file upload/download activities from new IP addresses. It establishes a baseline of typical behavior, compares it to recent activity, and flags deviations exceeding a default threshold of 25." - } - } - ] - }, - { - "name": "analytic14", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - SharePointFileOperation via devices with previously unseen user agents", - "elements": [ - { - "name": "analytic14-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Identifies anomalies if the number of documents uploaded or downloaded from device(s) associated with a previously unseen user agent exceeds a threshold (default is 5) and deviation (default is 25%)." - } - } - ] - }, - { - "name": "analytic15", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold", - "elements": [ - { - "name": "analytic15-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Identifies Office365 Sharepoint File Transfers above a certain threshold in a 15-minute time period.\nPlease note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur." - } - } - ] - }, - { - "name": "analytic16", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold", - "elements": [ - { - "name": "analytic16-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Identifies Office365 Sharepoint File Transfers with a distinct folder count above a certain threshold in a 15-minute time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur." - } - } - ] - }, - { - "name": "analytic17", - "type": "Microsoft.Common.Section", - "label": "Detect Abnormal Deny Rate for Source to Destination IP", - "elements": [ - { - "name": "analytic17-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Identifies abnormal deny rate for specific source IP to destination IP based on the normal average and standard deviation learned during a configured period. This can indicate potential exfiltration, initial access, or C2, where an attacker tries to exploit the same vulnerability on machines in the organization but is being blocked by firewall rules." - } - } - ] - }, - { - "name": "analytic18", - "type": "Microsoft.Common.Section", - "label": "Detect Protocol Changes for Destination Ports", - "elements": [ - { - "name": "analytic18-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Identifies changes in the protocol used for specific destination ports, comparing the current runtime with a learned baseline. This can indicate potential protocol misuse or configuration changes." - } - } - ] - }, - { - "name": "analytic19", - "type": "Microsoft.Common.Section", - "label": "Detect Source IP Scanning Multiple Open Ports", - "elements": [ - { - "name": "analytic19-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Identifies a source IP scanning multiple open ports on Global Secure Access Firewall. This can indicate malicious scanning of ports by an attacker, trying to reveal open ports in the organization that can be compromised for initial access." - } - } - ] - } - ] - }, - { - "name": "huntingqueries", - "label": "Hunting Queries", - "bladeTitle": "Hunting Queries", - "elements": [ - { - "name": "huntingqueries-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This solution installs the following hunting queries. After installing the solution, run these hunting queries to hunt for threats in the Manage solution view." - } - }, - { - "name": "huntingqueries-link", - "type": "Microsoft.Common.TextBlock", - "options": { - "link": { - "label": "Learn more", - "uri": "https://docs.microsoft.com/azure/sentinel/hunting" - } - } - }, - { - "name": "huntingquery1", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Anomalous access to other users' mailboxes", - "elements": [ - { - "name": "huntingquery1-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Looks for users accessing multiple other users' mailboxes or accessing multiple folders in another users mailbox. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery2", - "type": "Microsoft.Common.Section", - "label": "External User Added and Removed in a Short Timeframe", - "elements": [ - { - "name": "huntingquery2-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This hunting query identifies external user accounts that are added to a Team and then removed within one hour. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery3", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - External user from a new organisation added to Teams", - "elements": [ - { - "name": "huntingquery3-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This query identifies external users added to Teams where the user's domain is not one previously seen in Teams data. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery4", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule", - "elements": [ - { - "name": "huntingquery4-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Identifies when Exchange Online transport rule is configured to forward emails.\nThis could be an adversary mailbox configured to collect mail from multiple user accounts. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery5", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Bots added to multiple teams", - "elements": [ - { - "name": "huntingquery5-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This hunting query helps identify bots added to multiple Teams in a short space of time. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery6", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - User made Owner of multiple teams", - "elements": [ - { - "name": "huntingquery6-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This hunting query identifies users who have been made Owner of multiple Teams. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery7", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Multiple Teams deleted by a single user", - "elements": [ - { - "name": "huntingquery7-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This hunting query identifies where multiple Teams have been deleted by a single user in a short timeframe. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery8", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Previously Unseen Bot or Application Added to Teams", - "elements": [ - { - "name": "huntingquery8-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This hunting query helps identify new, and potentially unapproved applications or bots being added to Teams. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery9", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - New Windows Reserved Filenames staged on Office file services", - "elements": [ - { - "name": "huntingquery9-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This identifies new Windows Reserved Filenames on Office services like SharePoint and OneDrive in the past 7 days. It also detects when a user uploads these files to another user's workspace, which may indicate malicious activity. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery10", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Office Mail Forwarding - Hunting Version", - "elements": [ - { - "name": "huntingquery10-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Adversaries often abuse email-forwarding rules to monitor victim activities, steal information, and gain intelligence on the victim or their organization. This query highlights cases where user mail is being forwarded, including to external domains. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery11", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Files uploaded to teams and access summary", - "elements": [ - { - "name": "huntingquery11-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This hunting query identifies files uploaded to SharePoint via a Teams chat and\nsummarizes users and IP addresses that have accessed these files. This allows for \nidentification of anomalous file sharing patterns. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery12", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - User added to Teams and immediately uploads file", - "elements": [ - { - "name": "huntingquery12-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This hunting query identifies users who are added to a Teams Channel or Teams chat\nand within 1 minute of being added upload a file via the chat. This might be\nan indicator of suspicious activity. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery13", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Windows Reserved Filenames Staged on Office File Services", - "elements": [ - { - "name": "huntingquery13-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This identifies Windows Reserved Filenames on Office services like SharePoint and OneDrive. It also detects when a user uploads these files to another user's workspace, which may indicate malicious activity. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery14", - "type": "Microsoft.Common.Section", - "label": "Exes with double file extension and access summary", - "elements": [ - { - "name": "huntingquery14-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Provides a summary of executable files with double file extensions in SharePoint \n and the users and IP addresses that have accessed them. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery15", - "type": "Microsoft.Common.Section", - "label": "New Admin Account Activity Seen Which Was Not Seen Historically", - "elements": [ - { - "name": "huntingquery15-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "This will help you discover any new admin account activity which was seen and were not seen historically.\nAny new accounts seen in the results can be validated and investigated for any suspicious activities. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery16", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - SharePointFileOperation via previously unseen IPs", - "elements": [ - { - "name": "huntingquery16-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Shows SharePoint upload/download volume by IPs with high-risk ASNs. New IPs with volume spikes may be unauthorized and exfiltrating documents. This hunting query depends on AzureActiveDirectory AzureActiveDirectory data connector (SigninLogs EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery17", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 -SharePointFileOperation via devices with previously unseen user agents", - "elements": [ - { - "name": "huntingquery17-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Tracking via user agent is one way to differentiate between types of connecting device.\nIn homogeneous enterprise environments the user agent associated with an attacker device may stand out as unusual. This hunting query depends on AzureActiveDirectory AzureActiveDirectory data connector (SigninLogs EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery18", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Non-owner mailbox login activity", - "elements": [ - { - "name": "huntingquery18-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Finds non-owner mailbox access by admin/delegate permissions. Whitelist valid users and check others for unauthorized access. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery19", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - PowerShell or non-browser mailbox login activity", - "elements": [ - { - "name": "huntingquery19-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Detects mailbox login from Exchange PowerShell. All accounts can use it by default, but admins can change it. Whitelist benign activities. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery20", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - SharePoint File Operation via Client IP with Previously Unseen User Agents", - "elements": [ - { - "name": "huntingquery20-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "New user agents associated with a client IP for SharePoint file uploads/downloads. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - }, - { - "name": "huntingquery21", - "type": "Microsoft.Common.Section", - "label": "GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination", - "elements": [ - { - "name": "huntingquery21-text", - "type": "Microsoft.Common.TextBlock", - "options": { - "text": "Identifies when multiple (more than one) users' mailboxes are configured to forward to the same destination. \nThis could be an attacker-controlled destination mailbox configured to collect mail from multiple compromised user accounts. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" - } - } - ] - } - ] - } - ], - "outputs": { - "workspace-location": "[first(map(filter(basics('getLAWorkspace').value, (filter) => and(contains(toLower(filter.id), toLower(resourceGroup().name)),equals(filter.name,basics('workspace')))), (item) => item.location))]", - "location": "[location()]", - "workspace": "[basics('workspace')]" - } - } -} diff --git a/Solutions/Global Secure Access/Package/mainTemplate.json b/Solutions/Global Secure Access/Package/mainTemplate.json deleted file mode 100644 index 18a4637428..0000000000 --- a/Solutions/Global Secure Access/Package/mainTemplate.json +++ /dev/null @@ -1,5028 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "author": "Microsoft - support@microsoft.com", - "comments": "Solution template for Global Secure Access" - }, - "parameters": { - "location": { - "type": "string", - "minLength": 1, - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Not used, but needed to pass arm-ttk test `Location-Should-Not-Be-Hardcoded`. We instead use the `workspace-location` which is derived from the LA workspace" - } - }, - "workspace-location": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "[concat('Region to deploy solution resources -- separate from location selection',parameters('location'))]" - } - }, - "workspace": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Workspace name for Log Analytics where Microsoft Sentinel is setup" - } - }, - "workbook1-name": { - "type": "string", - "defaultValue": "Microsoft Global Secure Access Enriched M365 Logs", - "minLength": 1, - "metadata": { - "description": "Name for the workbook" - } - }, - "workbook2-name": { - "type": "string", - "defaultValue": "Microsoft Global Secure Access Traffic Logs", - "minLength": 1, - "metadata": { - "description": "Name for the workbook" - } - } - }, - "variables": { - "email": "support@microsoft.com", - "_email": "[variables('email')]", - "_solutionName": "Global Secure Access", - "_solutionVersion": "3.1.0", - "solutionId": "azuresentinel.azure-sentinel-solution-globalsecureaccess", - "_solutionId": "[variables('solutionId')]", - "workbookVersion1": "1.0.1", - "workbookContentId1": "GSAM365EnrichedEvents", - "workbookId1": "[resourceId('Microsoft.Insights/workbooks', variables('workbookContentId1'))]", - "workbookTemplateSpecName1": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-wb-',uniquestring(variables('_workbookContentId1'))))]", - "_workbookContentId1": "[variables('workbookContentId1')]", - "workspaceResourceId": "[resourceId('microsoft.OperationalInsights/Workspaces', parameters('workspace'))]", - "_workbookcontentProductId1": "[concat(take(variables('_solutionId'),50),'-','wb','-', uniqueString(concat(variables('_solutionId'),'-','Workbook','-',variables('_workbookContentId1'),'-', variables('workbookVersion1'))))]", - "workbookVersion2": "1.0.1", - "workbookContentId2": "GSANetworkTraffic", - "workbookId2": "[resourceId('Microsoft.Insights/workbooks', variables('workbookContentId2'))]", - "workbookTemplateSpecName2": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-wb-',uniquestring(variables('_workbookContentId2'))))]", - "_workbookContentId2": "[variables('workbookContentId2')]", - "_workbookcontentProductId2": "[concat(take(variables('_solutionId'),50),'-','wb','-', uniqueString(concat(variables('_solutionId'),'-','Workbook','-',variables('_workbookContentId2'),'-', variables('workbookVersion2'))))]", - "analyticRuleObject1": { - "analyticRuleVersion1": "1.0.0", - "_analyticRulecontentId1": "4c9f0a9e-44d7-4c9b-b7f0-f6a6e0d8f8fa", - "analyticRuleId1": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '4c9f0a9e-44d7-4c9b-b7f0-f6a6e0d8f8fa')]", - "analyticRuleTemplateSpecName1": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('4c9f0a9e-44d7-4c9b-b7f0-f6a6e0d8f8fa')))]", - "_analyticRulecontentProductId1": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','4c9f0a9e-44d7-4c9b-b7f0-f6a6e0d8f8fa','-', '1.0.0')))]" - }, - "analyticRuleObject2": { - "analyticRuleVersion2": "1.0.0", - "_analyticRulecontentId2": "57abf863-1c1e-46c6-85b2-35370b712c1e", - "analyticRuleId2": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '57abf863-1c1e-46c6-85b2-35370b712c1e')]", - "analyticRuleTemplateSpecName2": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('57abf863-1c1e-46c6-85b2-35370b712c1e')))]", - "_analyticRulecontentProductId2": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','57abf863-1c1e-46c6-85b2-35370b712c1e','-', '1.0.0')))]" - }, - "analyticRuleObject3": { - "analyticRuleVersion3": "2.0.8", - "_analyticRulecontentId3": "dc451755-8ab3-4059-b805-e454c45d1d44", - "analyticRuleId3": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'dc451755-8ab3-4059-b805-e454c45d1d44')]", - "analyticRuleTemplateSpecName3": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('dc451755-8ab3-4059-b805-e454c45d1d44')))]", - "_analyticRulecontentProductId3": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','dc451755-8ab3-4059-b805-e454c45d1d44','-', '2.0.8')))]" - }, - "analyticRuleObject4": { - "analyticRuleVersion4": "2.1.3", - "_analyticRulecontentId4": "4d38f80f-6b7d-4a1f-aeaf-e38df637e6ac", - "analyticRuleId4": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '4d38f80f-6b7d-4a1f-aeaf-e38df637e6ac')]", - "analyticRuleTemplateSpecName4": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('4d38f80f-6b7d-4a1f-aeaf-e38df637e6ac')))]", - "_analyticRulecontentProductId4": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','4d38f80f-6b7d-4a1f-aeaf-e38df637e6ac','-', '2.1.3')))]" - }, - "analyticRuleObject5": { - "analyticRuleVersion5": "2.1.4", - "_analyticRulecontentId5": "1a8f1297-23a4-4f09-a20b-90af8fc3641a", - "analyticRuleId5": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '1a8f1297-23a4-4f09-a20b-90af8fc3641a')]", - "analyticRuleTemplateSpecName5": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('1a8f1297-23a4-4f09-a20b-90af8fc3641a')))]", - "_analyticRulecontentProductId5": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','1a8f1297-23a4-4f09-a20b-90af8fc3641a','-', '2.1.4')))]" - }, - "analyticRuleObject6": { - "analyticRuleVersion6": "2.1.4", - "_analyticRulecontentId6": "edcfc2e0-3134-434c-8074-9101c530d419", - "analyticRuleId6": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'edcfc2e0-3134-434c-8074-9101c530d419')]", - "analyticRuleTemplateSpecName6": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('edcfc2e0-3134-434c-8074-9101c530d419')))]", - "_analyticRulecontentProductId6": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','edcfc2e0-3134-434c-8074-9101c530d419','-', '2.1.4')))]" - }, - "analyticRuleObject7": { - "analyticRuleVersion7": "2.0.6", - "_analyticRulecontentId7": "a9c76c8d-f60d-49ec-9b1f-bdfee6db3807", - "analyticRuleId7": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'a9c76c8d-f60d-49ec-9b1f-bdfee6db3807')]", - "analyticRuleTemplateSpecName7": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('a9c76c8d-f60d-49ec-9b1f-bdfee6db3807')))]", - "_analyticRulecontentProductId7": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','a9c76c8d-f60d-49ec-9b1f-bdfee6db3807','-', '2.0.6')))]" - }, - "analyticRuleObject8": { - "analyticRuleVersion8": "2.0.6", - "_analyticRulecontentId8": "db60e4b6-a845-4f28-a18c-94ebbaad6c6c", - "analyticRuleId8": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'db60e4b6-a845-4f28-a18c-94ebbaad6c6c')]", - "analyticRuleTemplateSpecName8": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('db60e4b6-a845-4f28-a18c-94ebbaad6c6c')))]", - "_analyticRulecontentProductId8": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','db60e4b6-a845-4f28-a18c-94ebbaad6c6c','-', '2.0.6')))]" - }, - "analyticRuleObject9": { - "analyticRuleVersion9": "2.0.5", - "_analyticRulecontentId9": "d75e8289-d1cb-44d4-bd59-2f44a9172478", - "analyticRuleId9": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'd75e8289-d1cb-44d4-bd59-2f44a9172478')]", - "analyticRuleTemplateSpecName9": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('d75e8289-d1cb-44d4-bd59-2f44a9172478')))]", - "_analyticRulecontentProductId9": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','d75e8289-d1cb-44d4-bd59-2f44a9172478','-', '2.0.5')))]" - }, - "analyticRuleObject10": { - "analyticRuleVersion10": "2.0.6", - "_analyticRulecontentId10": "0f1f2b17-f9d6-4d2a-a0fb-a7ae1659e3eb", - "analyticRuleId10": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '0f1f2b17-f9d6-4d2a-a0fb-a7ae1659e3eb')]", - "analyticRuleTemplateSpecName10": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('0f1f2b17-f9d6-4d2a-a0fb-a7ae1659e3eb')))]", - "_analyticRulecontentProductId10": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','0f1f2b17-f9d6-4d2a-a0fb-a7ae1659e3eb','-', '2.0.6')))]" - }, - "analyticRuleObject11": { - "analyticRuleVersion11": "2.0.7", - "_analyticRulecontentId11": "178c62b4-d5e5-40f5-8eab-7fccd0051e7a", - "analyticRuleId11": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '178c62b4-d5e5-40f5-8eab-7fccd0051e7a')]", - "analyticRuleTemplateSpecName11": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('178c62b4-d5e5-40f5-8eab-7fccd0051e7a')))]", - "_analyticRulecontentProductId11": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','178c62b4-d5e5-40f5-8eab-7fccd0051e7a','-', '2.0.7')))]" - }, - "analyticRuleObject12": { - "analyticRuleVersion12": "2.0.7", - "_analyticRulecontentId12": "433c254d-4b84-46f7-99ec-9dfefb5f6a7b", - "analyticRuleId12": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '433c254d-4b84-46f7-99ec-9dfefb5f6a7b')]", - "analyticRuleTemplateSpecName12": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('433c254d-4b84-46f7-99ec-9dfefb5f6a7b')))]", - "_analyticRulecontentProductId12": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','433c254d-4b84-46f7-99ec-9dfefb5f6a7b','-', '2.0.7')))]" - }, - "analyticRuleObject13": { - "analyticRuleVersion13": "2.0.6", - "_analyticRulecontentId13": "7460e34e-4c99-47b2-b7c0-c42e339fc586", - "analyticRuleId13": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '7460e34e-4c99-47b2-b7c0-c42e339fc586')]", - "analyticRuleTemplateSpecName13": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('7460e34e-4c99-47b2-b7c0-c42e339fc586')))]", - "_analyticRulecontentProductId13": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','7460e34e-4c99-47b2-b7c0-c42e339fc586','-', '2.0.6')))]" - }, - "analyticRuleObject14": { - "analyticRuleVersion14": "2.2.6", - "_analyticRulecontentId14": "efd17c5f-5167-40f8-a1e9-0818940785d9", - "analyticRuleId14": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'efd17c5f-5167-40f8-a1e9-0818940785d9')]", - "analyticRuleTemplateSpecName14": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('efd17c5f-5167-40f8-a1e9-0818940785d9')))]", - "_analyticRulecontentProductId14": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','efd17c5f-5167-40f8-a1e9-0818940785d9','-', '2.2.6')))]" - }, - "analyticRuleObject15": { - "analyticRuleVersion15": "1.0.5", - "_analyticRulecontentId15": "30375d00-68cc-4f95-b89a-68064d566358", - "analyticRuleId15": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '30375d00-68cc-4f95-b89a-68064d566358')]", - "analyticRuleTemplateSpecName15": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('30375d00-68cc-4f95-b89a-68064d566358')))]", - "_analyticRulecontentProductId15": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','30375d00-68cc-4f95-b89a-68064d566358','-', '1.0.5')))]" - }, - "analyticRuleObject16": { - "analyticRuleVersion16": "2.0.7", - "_analyticRulecontentId16": "abd6976d-8f71-4851-98c4-4d086201319c", - "analyticRuleId16": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'abd6976d-8f71-4851-98c4-4d086201319c')]", - "analyticRuleTemplateSpecName16": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('abd6976d-8f71-4851-98c4-4d086201319c')))]", - "_analyticRulecontentProductId16": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','abd6976d-8f71-4851-98c4-4d086201319c','-', '2.0.7')))]" - }, - "analyticRuleObject17": { - "analyticRuleVersion17": "1.0.0", - "_analyticRulecontentId17": "e3b6a9e7-4c3a-45e6-8baf-1d3bfa8e0c2b", - "analyticRuleId17": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'e3b6a9e7-4c3a-45e6-8baf-1d3bfa8e0c2b')]", - "analyticRuleTemplateSpecName17": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('e3b6a9e7-4c3a-45e6-8baf-1d3bfa8e0c2b')))]", - "_analyticRulecontentProductId17": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','e3b6a9e7-4c3a-45e6-8baf-1d3bfa8e0c2b','-', '1.0.0')))]" - }, - "analyticRuleObject18": { - "analyticRuleVersion18": "1.0.0", - "_analyticRulecontentId18": "f6a8d6a5-3e9f-47c8-a8d5-1b2b9d3b7d6a", - "analyticRuleId18": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'f6a8d6a5-3e9f-47c8-a8d5-1b2b9d3b7d6a')]", - "analyticRuleTemplateSpecName18": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('f6a8d6a5-3e9f-47c8-a8d5-1b2b9d3b7d6a')))]", - "_analyticRulecontentProductId18": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','f6a8d6a5-3e9f-47c8-a8d5-1b2b9d3b7d6a','-', '1.0.0')))]" - }, - "analyticRuleObject19": { - "analyticRuleVersion19": "1.0.0", - "_analyticRulecontentId19": "82cfa6b9-5f7e-4b8b-8b2f-a63f21b7a7d1", - "analyticRuleId19": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '82cfa6b9-5f7e-4b8b-8b2f-a63f21b7a7d1')]", - "analyticRuleTemplateSpecName19": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('82cfa6b9-5f7e-4b8b-8b2f-a63f21b7a7d1')))]", - "_analyticRulecontentProductId19": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','82cfa6b9-5f7e-4b8b-8b2f-a63f21b7a7d1','-', '1.0.0')))]" - }, - "huntingQueryObject1": { - "huntingQueryVersion1": "2.0.2", - "_huntingQuerycontentId1": "271e8881-3044-4332-a5f4-42264c2e0315", - "huntingQueryTemplateSpecName1": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('271e8881-3044-4332-a5f4-42264c2e0315')))]" - }, - "huntingQueryObject2": { - "huntingQueryVersion2": "2.0.1", - "_huntingQuerycontentId2": "119d9e1c-afcc-4d23-b239-cdb4e7bf851c", - "huntingQueryTemplateSpecName2": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('119d9e1c-afcc-4d23-b239-cdb4e7bf851c')))]" - }, - "huntingQueryObject3": { - "huntingQueryVersion3": "2.0.2", - "_huntingQuerycontentId3": "6fce5baf-bfc2-4c56-a6b7-9c4733fc5a45", - "huntingQueryTemplateSpecName3": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('6fce5baf-bfc2-4c56-a6b7-9c4733fc5a45')))]" - }, - "huntingQueryObject4": { - "huntingQueryVersion4": "2.0.2", - "_huntingQuerycontentId4": "9891684a-1e3a-4546-9403-3439513cbc70", - "huntingQueryTemplateSpecName4": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('9891684a-1e3a-4546-9403-3439513cbc70')))]" - }, - "huntingQueryObject5": { - "huntingQueryVersion5": "2.0.1", - "_huntingQuerycontentId5": "9eb64924-ec8d-44d0-b1f2-10665150fb74", - "huntingQueryTemplateSpecName5": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('9eb64924-ec8d-44d0-b1f2-10665150fb74')))]" - }, - "huntingQueryObject6": { - "huntingQueryVersion6": "2.0.2", - "_huntingQuerycontentId6": "558f15dd-3171-4b11-bf24-31c0610a20e0", - "huntingQueryTemplateSpecName6": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('558f15dd-3171-4b11-bf24-31c0610a20e0')))]" - }, - "huntingQueryObject7": { - "huntingQueryVersion7": "2.0.2", - "_huntingQuerycontentId7": "64990414-b015-4edf-bef0-343b741e68c5", - "huntingQueryTemplateSpecName7": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('64990414-b015-4edf-bef0-343b741e68c5')))]" - }, - "huntingQueryObject8": { - "huntingQueryVersion8": "2.0.2", - "_huntingQuerycontentId8": "bf76e508-9282-4cf1-9cc1-5c20c3dea2ee", - "huntingQueryTemplateSpecName8": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('bf76e508-9282-4cf1-9cc1-5c20c3dea2ee')))]" - }, - "huntingQueryObject9": { - "huntingQueryVersion9": "2.0.2", - "_huntingQuerycontentId9": "641ecd2d-27c9-4f05-8433-8205096b09fc", - "huntingQueryTemplateSpecName9": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('641ecd2d-27c9-4f05-8433-8205096b09fc')))]" - }, - "huntingQueryObject10": { - "huntingQueryVersion10": "2.0.2", - "_huntingQuerycontentId10": "d49fc965-aef3-49f6-89ad-10cc4697eb5b", - "huntingQueryTemplateSpecName10": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('d49fc965-aef3-49f6-89ad-10cc4697eb5b')))]" - }, - "huntingQueryObject11": { - "huntingQueryVersion11": "2.0.2", - "_huntingQuerycontentId11": "90e198a9-efb6-4719-ad89-81b8e93633a7", - "huntingQueryTemplateSpecName11": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('90e198a9-efb6-4719-ad89-81b8e93633a7')))]" - }, - "huntingQueryObject12": { - "huntingQueryVersion12": "2.0.2", - "_huntingQuerycontentId12": "3d6d0c04-7337-40cf-ace6-c471d442356d", - "huntingQueryTemplateSpecName12": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('3d6d0c04-7337-40cf-ace6-c471d442356d')))]" - }, - "huntingQueryObject13": { - "huntingQueryVersion13": "2.0.2", - "_huntingQuerycontentId13": "61c28cd7-3139-4731-8ea7-2cbbeabb4684", - "huntingQueryTemplateSpecName13": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('61c28cd7-3139-4731-8ea7-2cbbeabb4684')))]" - }, - "huntingQueryObject14": { - "huntingQueryVersion14": "2.0.1", - "_huntingQuerycontentId14": "d12580c2-1474-4125-a8a3-553f50d91215", - "huntingQueryTemplateSpecName14": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('d12580c2-1474-4125-a8a3-553f50d91215')))]" - }, - "huntingQueryObject15": { - "huntingQueryVersion15": "2.0.1", - "_huntingQuerycontentId15": "723c5f46-133f-4f1e-ada6-5c138f811d75", - "huntingQueryTemplateSpecName15": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('723c5f46-133f-4f1e-ada6-5c138f811d75')))]" - }, - "huntingQueryObject16": { - "huntingQueryVersion16": "2.0.2", - "_huntingQuerycontentId16": "e3d24cfd-b2a1-4ba7-8f80-0360892f9d57", - "huntingQueryTemplateSpecName16": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('e3d24cfd-b2a1-4ba7-8f80-0360892f9d57')))]" - }, - "huntingQueryObject17": { - "huntingQueryVersion17": "2.0.2", - "_huntingQuerycontentId17": "f2367171-1514-4c67-88ef-27434b6a1093", - "huntingQueryTemplateSpecName17": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('f2367171-1514-4c67-88ef-27434b6a1093')))]" - }, - "huntingQueryObject18": { - "huntingQueryVersion18": "2.0.1", - "_huntingQuerycontentId18": "0a8f410d-38b5-4d75-90da-32b472b97230", - "huntingQueryTemplateSpecName18": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('0a8f410d-38b5-4d75-90da-32b472b97230')))]" - }, - "huntingQueryObject19": { - "huntingQueryVersion19": "2.0.2", - "_huntingQuerycontentId19": "49a4f65a-fe18-408e-afec-042fde93d3ce", - "huntingQueryTemplateSpecName19": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('49a4f65a-fe18-408e-afec-042fde93d3ce')))]" - }, - "huntingQueryObject20": { - "huntingQueryVersion20": "2.0.2", - "_huntingQuerycontentId20": "e8ae1375-4640-430c-ae8e-2514d09c71eb", - "huntingQueryTemplateSpecName20": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('e8ae1375-4640-430c-ae8e-2514d09c71eb')))]" - }, - "huntingQueryObject21": { - "huntingQueryVersion21": "2.0.2", - "_huntingQuerycontentId21": "a1551ae4-f61c-4bca-9c57-4d0d681db2e9", - "huntingQueryTemplateSpecName21": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('a1551ae4-f61c-4bca-9c57-4d0d681db2e9')))]" - }, - "_solutioncontentProductId": "[concat(take(variables('_solutionId'),50),'-','sl','-', uniqueString(concat(variables('_solutionId'),'-','Solution','-',variables('_solutionId'),'-', variables('_solutionVersion'))))]" - }, - "resources": [ - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('workbookTemplateSpecName1')]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "GSAM365EnrichedEvents Workbook with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('workbookVersion1')]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.Insights/workbooks", - "name": "[variables('workbookContentId1')]", - "location": "[parameters('workspace-location')]", - "kind": "shared", - "apiVersion": "2021-08-01", - "metadata": { - "description": "This Workbook provides a detailed view of Microsoft 365 log data, enriched with contextual information to enhance visibility into user activities and potential security threats." - }, - "properties": { - "displayName": "[parameters('workbook1-name')]", - "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"## Enriched Microsoft 365 logs Workbook (Preview)\\n---\\n\\nThe enriched Microsoft 365 logs provide information about Microsoft 365 workloads, so you can review network data and security events relevant to Microsoft 365 apps.\"},\"name\":\"text - 2\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"value::all\"],\"parameters\":[{\"id\":\"ff8b2a55-1849-4848-acf8-eab5452e9f10\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"LogAnalyticWorkspace\",\"label\":\"Log Analytic Workspace\",\"type\":5,\"description\":\"The Log Analytic Workspace In Which To Execute The Queries\",\"isRequired\":true,\"query\":\"resources\\r\\n| where type == \\\"microsoft.operationalinsights/workspaces\\\"\\r\\n| project id\",\"crossComponentResources\":[\"value::all\"],\"typeSettings\":{\"resourceTypeFilter\":{\"microsoft.operationalinsights/workspaces\":true},\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},{\"id\":\"f15f34d8-8e2d-4c39-8dee-be2f979c86a8\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TimeRange\",\"label\":\"Time Range\",\"type\":4,\"isRequired\":true,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":300000},{\"durationMs\":900000},{\"durationMs\":1800000},{\"durationMs\":3600000},{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000}],\"allowCustom\":true},\"timeContext\":{\"durationMs\":86400000},\"value\":{\"durationMs\":2592000000}},{\"id\":\"8bab511b-53b3-4220-9d1c-372345b06728\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Users\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| summarize Count = count() by UserId\\r\\n| order by Count desc, UserId asc\\r\\n| project Value = UserId, Label = strcat(UserId, ' - ', Count, ' Logs'), Selected = false\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"typeSettings\":{\"limitSelectTo\":20,\"additionalResourceOptions\":[\"value::all\"],\"selectAllValue\":\"*\",\"showDefault\":false},\"timeContext\":{\"durationMs\":0},\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":[\"value::all\"]}],\"style\":\"pills\",\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},\"name\":\"parameters - 15\"},{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"tabs\",\"tabStyle\":\"bigger\",\"links\":[{\"id\":\"e841bafb-6437-4d29-84ac-ba16c5a6d901\",\"cellValue\":\"selTab\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Overview\",\"subTarget\":\"Overview\",\"style\":\"link\"},{\"id\":\"ac5f2082-50bc-4739-bdf2-20c93b613671\",\"cellValue\":\"selTab\",\"linkTarget\":\"parameter\",\"linkLabel\":\"SharePoint/OneDrive Insights\",\"subTarget\":\"Threat\",\"style\":\"link\"},{\"id\":\"dc2778e7-739b-44ba-9ae4-c81901277f57\",\"cellValue\":\"selTab\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Exchange Online Insights\",\"subTarget\":\"ThreatEXO\",\"style\":\"link\"},{\"id\":\"666111e2-54ff-4fa4-a648-11a5c8c0235b\",\"cellValue\":\"selTab\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Teams Insights\",\"subTarget\":\"ThreatTeams\",\"style\":\"link\"}]},\"name\":\"links - 7\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| where Workload in (\\\"Exchange\\\")\\r\\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\\r\\n| extend \\r\\n Country = tostring(GeoInfo.country), \\r\\n State = tostring(GeoInfo.state), \\r\\n City = tostring(GeoInfo.city), \\r\\n Latitude = tostring(GeoInfo.latitude), \\r\\n Longitude = tostring(GeoInfo.longitude)\\r\\n| project \\r\\n UserId, \\r\\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\\r\\n Latitude, \\r\\n Longitude, \\r\\n City, \\r\\n State, \\r\\n Country\\r\\n| summarize Count = count() by City, State, Country\\r\\n\",\"size\":0,\"title\":\"Access By Location\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"Country\",\"latitude\":\"Latitude\",\"longitude\":\"Longitude\",\"sizeSettings\":\"Count\",\"sizeAggregation\":\"Sum\",\"labelSettings\":\"Country\",\"legendMetric\":\"Count\",\"legendAggregation\":\"Sum\",\"itemColorSettings\":{\"nodeColorField\":\"Count\",\"colorAggregation\":\"Sum\",\"type\":\"heatmap\",\"heatmapPalette\":\"turquoise\"}}},\"customWidth\":\"50\",\"name\":\"query - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| where Workload in (\\\"Exchange\\\")\\r\\n| summarize [\\\"Count\\\"] = count() by [\\\"Source IP\\\"] = SourceIp, [\\\"Device Operating System\\\"] = DeviceOperatingSystem\\r\\n| order by [\\\"Count\\\"] desc\\r\\n\",\"size\":0,\"title\":\"Access By Location And OS\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Operation in (\\\"Set-AdminAuditLogConfig\\\", \\\"Set-OrganizationConfig\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Audit Of Critical Configuration Changes\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Operation in (\\\"Set-TransportRule\\\", \\\"Set-AtpPolicyForO365\\\", \\\"Set-MalwareFilterRule\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Changes In Email Security Policies\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 3\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Operation in (\\\"Add-RoleGroupMember\\\", \\\"Remove-ManagementRoleAssignment\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Monitoring Role Group Membership Changes\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 4\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Operation in (\\\"New-TenantAllowBlockListSpoofitems\\\", \\\"Remove-TenantAllowBlockListSpoofitems\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Spoofing Settings Management Activities\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 5\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Operation in (\\\"New-SafeAttachmentPolicy\\\", \\\"Set-SafeAttachmentPolicy\\\", \\\"Set-SafeAttachmentRule\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Safe Attachments Policy Changes\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 6\"}]},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"ThreatEXO\"},\"name\":\"group - 18\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs | where Workload == \\\"Exchange\\\" | where Operation in (\\\"Add-MailboxFolderPermission\\\", \\\"Add-RoleGroupMember\\\", \\\"New-TransportRule\\\", \\\"Remove-ManagementRoleAssignment\\\", \\\"Set-TransportRule\\\") | summarize Count = count(), Users = makeset(UserId) by Operation | order by Count desc\",\"size\":0,\"title\":\"Permission changes and security policy updates\",\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"conditionalVisibility\":{\"parameterName\":\"seltab\",\"comparison\":\"isEqualTo\",\"value\":\"ThreatEXO\"},\"name\":\"query - 13\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| where Workload in (\\\"Teams\\\")\\r\\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\\r\\n| extend \\r\\n Country = tostring(GeoInfo.country), \\r\\n State = tostring(GeoInfo.state), \\r\\n City = tostring(GeoInfo.city), \\r\\n Latitude = tostring(GeoInfo.latitude), \\r\\n Longitude = tostring(GeoInfo.longitude)\\r\\n| project \\r\\n UserId, \\r\\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\\r\\n Latitude, \\r\\n Longitude, \\r\\n City, \\r\\n State, \\r\\n Country\\r\\n| summarize Count = count() by City, State, Country\\r\\n\",\"size\":0,\"title\":\"Access By Location\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"Country\",\"latitude\":\"Latitude\",\"longitude\":\"Longitude\",\"sizeSettings\":\"Count\",\"sizeAggregation\":\"Sum\",\"labelSettings\":\"Country\",\"legendMetric\":\"Count\",\"legendAggregation\":\"Sum\",\"itemColorSettings\":{\"nodeColorField\":\"Count\",\"colorAggregation\":\"Sum\",\"type\":\"heatmap\",\"heatmapPalette\":\"turquoise\"}}},\"customWidth\":\"50\",\"name\":\"query - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| where Workload in (\\\"Teams\\\")\\r\\n| summarize [\\\"Count\\\"] = count() by [\\\"Source IP\\\"] = SourceIp, [\\\"Device Operating System\\\"] = DeviceOperatingSystem\\r\\n| order by [\\\"Count\\\"] desc\\r\\n\",\"size\":0,\"title\":\"Access By Location And OS\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 1\"}]},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"ThreatTeams\"},\"name\":\"group - 10\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| where Workload in (\\\"OneDrive\\\", \\\"SharePoint\\\",\\\"SPO/OneDrive\\\")\\r\\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\\r\\n| extend \\r\\n Country = tostring(GeoInfo.country), \\r\\n State = tostring(GeoInfo.state), \\r\\n City = tostring(GeoInfo.city), \\r\\n Latitude = tostring(GeoInfo.latitude), \\r\\n Longitude = tostring(GeoInfo.longitude)\\r\\n| project \\r\\n UserId, \\r\\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\\r\\n Latitude, \\r\\n Longitude, \\r\\n City, \\r\\n State, \\r\\n Country\\r\\n| summarize Count = count() by City, State, Country\\r\\n\",\"size\":0,\"title\":\"Access By Location\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"Country\",\"latitude\":\"Latitude\",\"longitude\":\"Longitude\",\"sizeSettings\":\"Count\",\"sizeAggregation\":\"Sum\",\"labelSettings\":\"Country\",\"legendMetric\":\"Country\",\"numberOfMetrics\":19,\"legendAggregation\":\"Count\",\"itemColorSettings\":{\"nodeColorField\":\"Count\",\"colorAggregation\":\"Sum\",\"type\":\"heatmap\",\"heatmapPalette\":\"turquoise\"}}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Threat\"},\"name\":\"query - 19\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| where Workload in (\\\"OneDrive\\\", \\\"SharePoint\\\", \\\"SPO/OneDrive\\\")\\r\\n| summarize [\\\"Event Count\\\"] = count() by [\\\"Source IP\\\"] = SourceIp, [\\\"Device Operating System\\\"] = DeviceOperatingSystem\\r\\n| order by [\\\"Event Count\\\"] desc\\r\\n\",\"size\":2,\"title\":\"Access By Location And OS\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"filter\":true}},\"customWidth\":\"50\",\"name\":\"query - 20\"}]},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Threat\"},\"name\":\"group - 20\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Workload == \\\"Teams\\\"\\r\\n| where Operation in (\\\"MemberAdded\\\", \\\"MemberRemoved\\\", \\\"TeamDeleted\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Changes In Team Memberships\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"ThreatTeams\"},\"customWidth\":\"50\",\"name\":\"query - 11\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let RiskyExtensions = dynamic([\\\"exe\\\", \\\"msi\\\", \\\"bat\\\", \\\"cmd\\\", \\\"com\\\", \\\"scr\\\", \\\"pif\\\", \\\"ps1\\\", \\\"vbs\\\", \\\"js\\\", \\\"jse\\\", \\\"wsf\\\", \\\"docm\\\", \\\"xlsm\\\", \\\"pptm\\\", \\\"dll\\\", \\\"ocx\\\", \\\"cpl\\\", \\\"app\\\", \\\"vb\\\", \\\"reg\\\", \\\"inf\\\", \\\"hta\\\"]);\\r\\n\\r\\nEnrichedMicrosoft365AuditLogs\\r\\n| where Workload in (\\\"SPO/OneDrive\\\")\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| extend [\\\"File Extension\\\"] = tostring(parse_json(AdditionalProperties).SourceFileExtension)\\r\\n| where [\\\"File Extension\\\"] in (RiskyExtensions)\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, [\\\"File Extension\\\"], Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], [\\\"File Extension\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], [\\\"File Extension\\\"], Operation, Count\\r\\n\",\"size\":0,\"title\":\"Risky File Operations\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"OperationCount\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\"}}]}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Threat\"},\"name\":\"query - 1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Workload in (\\\"OneDrive\\\", \\\"SharePoint\\\", \\\"SPO/OneDrive\\\")\\r\\n| where Operation in (\\\"FileRecycled\\\", \\\"FileDownloaded\\\", \\\"FileUploaded\\\", \\\"FileCreated\\\", \\\"File Modified\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Bulk File Events\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"OperationCount\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\"}}]}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Threat\"},\"name\":\"query - 8\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Workload in (\\\"SPO/OneDrive\\\")\\r\\n| where Operation == \\\"FileDeletedFirstStageRecycleBin\\\"\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by bin(Timestamp, 1h), [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n| where Count > 1\\r\\n\",\"size\":0,\"title\":\" Bulk File Deletion Operations\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"FileDeletions\",\"formatter\":8,\"formatOptions\":{\"palette\":\"blue\"}}]}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Threat\"},\"name\":\"query - 3\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let baselinePeriod = 30d;\\r\\nlet detectionWindow = 1h;\\r\\nlet downloadThreshold = 5; // Threshold of downloads indicating potential exfiltration\\r\\n\\r\\nEnrichedMicrosoft365AuditLogs\\r\\n| where TimeGenerated >= ago(baselinePeriod)\\r\\n| where Workload in (\\\"OneDrive\\\", \\\"SharePoint\\\", \\\"SPO/OneDrive\\\")\\r\\n| where Operation == \\\"FileDownloaded\\\"\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by bin(Timestamp, detectionWindow), [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Bulk File Download\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"DownloadCount\",\"formatter\":8,\"formatOptions\":{\"palette\":\"blue\"}}]}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Threat\"},\"name\":\"query - 4\"}]},\"name\":\"group - 9\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\\r\\n| extend \\r\\n Country = tostring(GeoInfo.country), \\r\\n State = tostring(GeoInfo.state), \\r\\n City = tostring(GeoInfo.city), \\r\\n Latitude = tostring(GeoInfo.latitude), \\r\\n Longitude = tostring(GeoInfo.longitude)\\r\\n| project \\r\\n UserId, \\r\\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\\r\\n Latitude, \\r\\n Longitude, \\r\\n City, \\r\\n State, \\r\\n Country\\r\\n | where tostring(Country) != \\\"\\\"\\r\\n| summarize Count = count() by City, State, Country\\r\\n\\r\\n\\r\\n\",\"size\":0,\"title\":\"Access By Location\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"Country\",\"latitude\":\"Latitude\",\"longitude\":\"Longitude\",\"sizeSettings\":\"Count\",\"sizeAggregation\":\"Sum\",\"minSize\":20,\"labelSettings\":\"Country\",\"legendMetric\":\"Country\",\"numberOfMetrics\":8,\"legendAggregation\":\"Count\",\"itemColorSettings\":{\"nodeColorField\":\"Count\",\"colorAggregation\":\"Sum\",\"type\":\"heatmap\",\"heatmapPalette\":\"turquoise\"}}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 5\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| summarize [\\\"Event Count\\\"] = count() by [\\\"Time Generated\\\"] = TimeGenerated, [\\\"User\\\"] = UserId, [\\\"Source IP\\\"] = SourceIp, [\\\"Device Operating System\\\"] = DeviceOperatingSystem, [\\\"Workload\\\"] = Workload\\r\\n| order by [\\\"Time Generated\\\"] desc\\r\\n\",\"size\":0,\"title\":\"Access By Location And OS\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"rowLimit\":1000,\"filter\":true}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 1\"}]},\"name\":\"group - 19\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let data = EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| project \\r\\n Operation, \\r\\n UserId, \\r\\n Workload, \\r\\n SourceIp, \\r\\n DeviceId, \\r\\n TimeGenerated, \\r\\n Details = pack_all(),\\r\\n OS = tostring(DeviceOperatingSystem)\\r\\n| extend Workload = tostring(Workload)\\r\\n| extend WorkloadStatus = case(\\r\\n Workload == \\\"Exchange\\\", \\\"Exchange\\\",\\r\\n Workload == \\\"Teams\\\", \\\"Teams\\\",\\r\\n Workload == \\\"SharePoint\\\", \\\"SharePoint\\\",\\r\\n \\\"Other\\\"\\r\\n);\\r\\n\\r\\nlet appData = data\\r\\n| summarize \\r\\n TotalCount = count(), \\r\\n ExchangeCount = countif(Workload == \\\"Exchange\\\"), \\r\\n TeamsCount = countif(Workload == \\\"Teams\\\"), \\r\\n SharePointCount = countif(Workload == \\\"SharePoint\\\"), \\r\\n OtherCount = countif(Workload == \\\"Other\\\") \\r\\n by UserId\\r\\n| where UserId != ''\\r\\n| join kind=inner (\\r\\n data\\r\\n | make-series Trend = count() default = 0 on TimeGenerated in range({TimeRange:start}, {TimeRange:end}, {TimeRange:grain}) by UserId\\r\\n | project-away TimeGenerated\\r\\n ) on UserId\\r\\n| order by TotalCount desc, UserId asc\\r\\n| project UserId, TotalCount, ExchangeCount, TeamsCount, SharePointCount, OtherCount, Trend\\r\\n| serialize Id = row_number();\\r\\n\\r\\ndata\\r\\n| summarize \\r\\n TotalCount = count(), \\r\\n ExchangeCount = countif(Workload == \\\"Exchange\\\"), \\r\\n TeamsCount = countif(Workload == \\\"Teams\\\"), \\r\\n SharePointCount = countif(Workload == \\\"SharePoint\\\"), \\r\\n OtherCount = countif(Workload == \\\"Other\\\") \\r\\n by UserId, SourceIp = tostring(SourceIp)\\r\\n| join kind=inner (\\r\\n data\\r\\n | make-series Trend = count() default = 0 on TimeGenerated in range({TimeRange:start}, {TimeRange:end}, {TimeRange:grain}) by UserId, SourceIp\\r\\n | project-away TimeGenerated\\r\\n ) on UserId, SourceIp\\r\\n| order by TotalCount desc, UserId asc\\r\\n| project UserId, SourceIp, TotalCount, ExchangeCount, TeamsCount, SharePointCount, OtherCount, Trend\\r\\n| serialize Id = row_number(1000000)\\r\\n| join kind=inner (appData) on UserId\\r\\n| project Id, Name = SourceIp, Type = 'Client IP', ['Events Count'] = TotalCount, Trend, ['Exchange Events'] = ExchangeCount, ['Teams Events'] = TeamsCount, ['SharePoint Events'] = SharePointCount, ['Other Count'] = OtherCount, ParentId = Id1\\r\\n| union (\\r\\n appData \\r\\n | project Id, Name = UserId, Type = 'Operating System', ['Events Count'] = TotalCount, Trend, ['Exchange Events'] = ExchangeCount, ['Teams Events'] = TeamsCount, ['SharePoint Events'] = SharePointCount, ['Other Count'] = OtherCount, ParentId = -1\\r\\n )\\r\\n| order by ['Events Count'] desc, Name asc\\r\\n\",\"size\":2,\"title\":\"Activity Log\",\"timeContextFromParameter\":\"TimeRange\",\"showRefreshButton\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Id\",\"formatter\":5},{\"columnMatch\":\"Type\",\"formatter\":5},{\"columnMatch\":\"Trend\",\"formatter\":9,\"formatOptions\":{\"palette\":\"blue\"}},{\"columnMatch\":\"Other Count\",\"formatter\":5},{\"columnMatch\":\"ParentId\",\"formatter\":5},{\"columnMatch\":\"Operation Count\",\"formatter\":8,\"formatOptions\":{\"palette\":\"blue\"}},{\"columnMatch\":\"Exchange Count\",\"formatter\":8,\"formatOptions\":{\"min\":0,\"palette\":\"blue\"}},{\"columnMatch\":\"Teams Count\",\"formatter\":8,\"formatOptions\":{\"min\":0,\"palette\":\"purple\"}},{\"columnMatch\":\"SharePoint Count\",\"formatter\":8,\"formatOptions\":{\"min\":0,\"palette\":\"turquoise\"}},{\"columnMatch\":\"Details\",\"formatter\":5,\"formatOptions\":{\"linkTarget\":\"GenericDetails\",\"linkIsContextBlade\":true}}],\"rowLimit\":1000,\"filter\":true,\"hierarchySettings\":{\"idColumn\":\"Id\",\"parentColumn\":\"ParentId\",\"treeType\":0,\"expanderColumn\":\"Name\"}}},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 6\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
\\r\\nšŸ’” _Click on a segment of the pie chart to explore more details_\"},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"text - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| extend \\r\\n OS = coalesce(DeviceOperatingSystem, \\\"Unknown OS\\\"),\\r\\n OSVersion = coalesce(tostring(DeviceOperatingSystemVersion), \\\"Unknown Version\\\")\\r\\n| summarize DeviceCount = count() by OS, OSVersion\\r\\n| order by DeviceCount desc\\r\\n\",\"size\":3,\"title\":\"Devices Accessing M365\",\"timeContextFromParameter\":\"TimeRange\",\"exportParameterName\":\"Parampie\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"piechart\"},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| extend \\r\\n [\\\"Operating System\\\"] = coalesce(DeviceOperatingSystem, \\\"Unknown OS\\\"),\\r\\n [\\\"OS Version\\\"] = coalesce(tostring(DeviceOperatingSystemVersion), \\\"Unknown Version\\\"),\\r\\n [\\\"Device ID\\\"] = coalesce(tostring(DeviceId), \\\"Unknown DeviceId\\\")\\r\\n| where [\\\"Operating System\\\"] == dynamic({Parampie}).label\\r\\n| summarize [\\\"Device Count\\\"] = count() by [\\\"Operating System\\\"], [\\\"OS Version\\\"], [\\\"Device ID\\\"]\\r\\n| order by [\\\"Device Count\\\"] desc\\r\\n\",\"size\":2,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"$gen_group\",\"formatter\":1}],\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"OperatingSystem\",\"OSVersion\"],\"expandTopLevel\":false}}},\"customWidth\":\"50\",\"conditionalVisibilities\":[{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},{\"parameterName\":\"Parampie\",\"comparison\":\"isNotEqualTo\"}],\"name\":\"query - 1\"}]},\"name\":\"group - 19\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let BusinessHoursStart = 8; // 8 AM\\r\\nlet BusinessHoursEnd = 18; // 6 PM\\r\\n\\r\\nEnrichedMicrosoft365AuditLogs \\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| extend HourOfDay = hourofday(TimeGenerated)\\r\\n| where HourOfDay < BusinessHoursStart or HourOfDay > BusinessHoursEnd\\r\\n| summarize [\\\"Off-Hour Activities\\\"] = count() by [\\\"User ID\\\"] = UserId, [\\\"Date\\\"] = bin(TimeGenerated, 1d), [\\\"Operation\\\"]\\r\\n| order by [\\\"Off-Hour Activities\\\"] desc\\r\\n\",\"size\":0,\"title\":\"Activity Outside Standard Working Hours (8:00 - 18:00)\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 14\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\n| summarize Count = count() by bin(TimeGenerated, 1h)\\n| order by TimeGenerated asc\\n\",\"size\":0,\"title\":\"Microsoft 365 Transactions\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"unstackedbar\"},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 2\"}],\"fallbackResourceIds\":[\"Global Secure Access\"],\"fromTemplateId\":\"GSA Enriched Microsoft 365 logs\",\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", - "version": "1.0", - "sourceId": "[variables('workspaceResourceId')]", - "category": "sentinel" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('Workbook-', last(split(variables('workbookId1'),'/'))))]", - "properties": { - "description": "@{workbookKey=GSAM365EnrichedEvents; logoFileName=gsa.svg; description=This Workbook provides a detailed view of Microsoft 365 log data, enriched with contextual information to enhance visibility into user activities and potential security threats.; dataTypesDependencies=System.Object[]; dataConnectorsDependencies=System.Object[]; previewImagesFileNames=System.Object[]; version=1.0.1; title=Microsoft Global Secure Access Enriched M365 Logs; templateRelativePath=GSAM365EnrichedEvents.json; provider=Microsoft}.description", - "parentId": "[variables('workbookId1')]", - "contentId": "[variables('_workbookContentId1')]", - "kind": "Workbook", - "version": "[variables('workbookVersion1')]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - }, - "dependencies": { - "operator": "AND", - "criteria": [ - { - "contentId": "AzureActiveDirectory", - "kind": "DataConnector" - } - ] - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('_workbookContentId1')]", - "contentKind": "Workbook", - "displayName": "[parameters('workbook1-name')]", - "contentProductId": "[variables('_workbookcontentProductId1')]", - "id": "[variables('_workbookcontentProductId1')]", - "version": "[variables('workbookVersion1')]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('workbookTemplateSpecName2')]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "GSANetworkTraffic Workbook with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('workbookVersion2')]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.Insights/workbooks", - "name": "[variables('workbookContentId2')]", - "location": "[parameters('workspace-location')]", - "kind": "shared", - "apiVersion": "2021-08-01", - "metadata": { - "description": "This workbook provides an overview of all traffic logs within your network, offering insights into data transfer, anomalies, and potential threats." - }, - "properties": { - "displayName": "[parameters('workbook2-name')]", - "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"## Network Traffic Insights Workbook (Preview)\\n---\\nInformation in the dashboard is based on log data\\n\\n\\n\"},\"name\":\"text - 2\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"value::all\"],\"parameters\":[{\"id\":\"ff8b2a55-1849-4848-acf8-eab5452e9f10\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"LogAnalyticWorkspace\",\"label\":\"Log Analytic Workspace\",\"type\":5,\"description\":\"The Log Analytic Workspace In Which To Execute The Queries\",\"isRequired\":true,\"query\":\"resources\\r\\n| where type == \\\"microsoft.operationalinsights/workspaces\\\"\\r\\n| project id\",\"crossComponentResources\":[\"value::all\"],\"typeSettings\":{\"resourceTypeFilter\":{\"microsoft.operationalinsights/workspaces\":true},\"showDefault\":false},\"timeContext\":{\"durationMs\":2592000000},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},{\"id\":\"f15f34d8-8e2d-4c39-8dee-be2f979c86a8\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TimeRange\",\"label\":\"Time Range\",\"type\":4,\"isRequired\":true,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":300000},{\"durationMs\":900000},{\"durationMs\":1800000},{\"durationMs\":3600000},{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000}],\"allowCustom\":true},\"timeContext\":{\"durationMs\":86400000},\"value\":{\"durationMs\":604800000}},{\"id\":\"8bab511b-53b3-4220-9d1c-372345b06728\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Users\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"NetworkAccessTraffic\\r\\n| summarize Count = count() by UserPrincipalName\\r\\n| order by Count desc, UserPrincipalName asc\\r\\n| project Value = UserPrincipalName, Label = strcat(UserPrincipalName, ' - ', Count, ' Logs'), Selected = false\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"typeSettings\":{\"limitSelectTo\":20,\"additionalResourceOptions\":[\"value::all\"],\"selectAllValue\":\"*\",\"showDefault\":false},\"timeContext\":{\"durationMs\":0},\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":[\"value::all\"]},{\"id\":\"527af4d2-3089-4aa4-9fbb-48ec697db20d\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"WebCategories\",\"label\":\"Web Categories\",\"type\":2,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"NetworkAccessTraffic\\r\\n| extend firstCategory = tostring(split(DestinationWebCategories, ',')[0]) // Split and get the first category\\r\\n| summarize Count = count() by firstCategory\\r\\n| order by Count desc, firstCategory asc\\r\\n| project Value = firstCategory, Label = strcat(firstCategory, ' - ', Count, ' Logs')\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"typeSettings\":{\"additionalResourceOptions\":[\"value::all\"],\"selectAllValue\":\"*\",\"showDefault\":false},\"timeContext\":{\"durationMs\":604800000},\"timeContextFromParameter\":\"TimeRange\",\"defaultValue\":\"value::all\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":[\"value::all\"]}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 15\"},{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"tabs\",\"links\":[{\"id\":\"2b2cd1be-9d25-412c-8444-f005c4789b55\",\"cellValue\":\"tabSel\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Overview\",\"subTarget\":\"Overview\",\"style\":\"link\"},{\"id\":\"cc3e67f2-f20f-4430-8dee-d0773b90d9ce\",\"cellValue\":\"tabSel\",\"linkTarget\":\"parameter\",\"linkLabel\":\"All Traffic\",\"subTarget\":\"AllTraffic\",\"style\":\"link\"},{\"id\":\"5ae54b5a-ac7b-4b7a-a1e1-1e574625caa3\",\"cellValue\":\"tabSel\",\"linkTarget\":\"parameter\",\"linkLabel\":\"URL Lookup\",\"subTarget\":\"URLLookup\",\"style\":\"link\"},{\"id\":\"68c566f8-957e-4a3f-8b66-730fc24135fb\",\"cellValue\":\"tabSel\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Security Insights\",\"subTarget\":\"Security\",\"style\":\"link\"}]},\"name\":\"links - 7\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let NetworkAccessTrafficData = NetworkAccessTraffic\\r\\n| where SourceIp != \\\"\\\" and isnotempty(SourceIp)\\r\\n| summarize arg_max(TimeGenerated, *) by SourceIp, TenantId;\\r\\n\\r\\nlet SigninLogsData = SigninLogs\\r\\n| where IPAddress != \\\"\\\" and isnotempty(IPAddress)\\r\\n| summarize arg_max(TimeGenerated, *) by IPAddress, TenantId, UserId, CorrelationId;\\r\\n\\r\\nSigninLogsData\\r\\n| join kind=leftanti (\\r\\n NetworkAccessTrafficData\\r\\n | where SourceIp != \\\"\\\" and isnotempty(SourceIp)\\r\\n) on $left.IPAddress == $right.SourceIp and $left.TenantId == $right.TenantId\\r\\n| project TimeGenerated, IPAddress, UserId, UserPrincipalName, AppDisplayName, DeviceDetail.deviceId\\r\\n\",\"size\":0,\"title\":\"Sign-ins Outside Global Secure Access\",\"timeContextFromParameter\":\"TimeRange\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"rowLimit\":1000,\"filter\":true}},\"name\":\"query - 0\"}]},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"Security\"},\"name\":\"group - 10\"},{\"type\":1,\"content\":{\"json\":\"šŸ’” Type the URL for detailed analysis\"},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"URLLookup\"},\"name\":\"text - 10\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"ec8c38c8-3064-4921-8dcd-a69d3895599b\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"URL\",\"type\":1,\"typeSettings\":{\"isSearchBox\":true},\"timeContext\":{\"durationMs\":86400000}}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"URLLookup\"},\"name\":\"parameters - 9\"},{\"type\":1,\"content\":{\"json\":\"# Web Categories\"},\"name\":\"text - 11\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let CategoryData = NetworkAccessTraffic \\r\\n| where DestinationFqdn contains \\\"{URL}\\\" // Filters logs based on the provided URL parameter\\r\\n| extend CategoriesList = split(DestinationWebCategories, \\\",\\\") // Split categories into a list\\r\\n| mv-expand Category = CategoriesList // Expand the list into individual rows\\r\\n| extend Category = trim_start(\\\" \\\", trim_end(\\\" \\\", tostring(Category))) // Trim leading and trailing spaces\\r\\n| extend Category = iff(TrafficType == \\\"microsoft365\\\", \\\"Microsoft M365\\\", Category) // Set category to \\\"Microsoft M365\\\" if TrafficType is \\\"microsoft365\\\"\\r\\n| summarize UniqueCategories = make_set(Category); // Create a set of unique categories\\r\\n\\r\\nCategoryData\\r\\n| extend \\r\\n PrimaryCategory = iff(array_length(UniqueCategories) > 0, tostring(UniqueCategories[0]), \\\"None\\\")\\r\\n| project \\r\\n col1 = PrimaryCategory,\\r\\n snapshot = \\\"Primary Category\\\"\\r\\n\\r\\n| union (\\r\\n CategoryData\\r\\n | extend \\r\\n SecondaryCategory = iff(array_length(UniqueCategories) > 1, tostring(UniqueCategories[1]), \\\"None\\\")\\r\\n | project \\r\\n col1 = SecondaryCategory,\\r\\n snapshot = \\\"Secondary Category\\\"\\r\\n)\\r\\n\\r\\n| order by snapshot asc\\r\\n\",\"size\":4,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"snapshot\"},\"leftContent\":{\"columnMatch\":\"col1\",\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"maximumFractionDigits\":2}}},\"showBorder\":true,\"size\":\"auto\"}},\"name\":\"query - 17\"},{\"type\":1,\"content\":{\"json\":\"# Traffic Access Details\"},\"name\":\"text - 15\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let NetworkData = NetworkAccessTraffic\\r\\n| where DestinationFqdn contains \\\"{URL}\\\"; // Filters logs based on the provided URL parameter\\r\\n\\r\\nNetworkData\\r\\n| summarize \\r\\n UniqueUsers = dcount(UserPrincipalName),\\r\\n UniqueDevices = dcount(DeviceId), \\r\\n TotalAllow = countif(Action == \\\"Allow\\\"),\\r\\n TotalBlock = countif(Action == \\\"Block\\\")\\r\\n| project \\r\\n col1 = UniqueUsers, \\r\\n snapshot = \\\"Unique Users\\\"\\r\\n| union (NetworkData\\r\\n | summarize UniqueDevices = dcount(DeviceId)\\r\\n | project col1 = UniqueDevices, snapshot = \\\"Unique Devices\\\")\\r\\n| union (NetworkData\\r\\n | summarize TotalAllow = countif(Action == \\\"Allow\\\")\\r\\n | project col1 = TotalAllow, snapshot = \\\"Total Allow\\\")\\r\\n| union (NetworkData\\r\\n | summarize TotalBlock = countif(Action == \\\"Block\\\")\\r\\n | project col1 = TotalBlock, snapshot = \\\"Total Block\\\")\\r\\n| order by snapshot asc\",\"size\":4,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"snapshot\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"col1\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"maximumFractionDigits\":2,\"maximumSignificantDigits\":3}}},\"showBorder\":true}},\"name\":\"query - 14\"},{\"type\":1,\"content\":{\"json\":\"# Bandwidth Usage\"},\"name\":\"text - 16\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let NetworkData = NetworkAccessTraffic\\r\\n| where DestinationFqdn contains \\\"{URL}\\\"; // Filters logs based on the provided URL parameter\\r\\n\\r\\nNetworkData\\r\\n| summarize \\r\\n TotalSent = sum(SentBytes),\\r\\n TotalReceived = sum(ReceivedBytes),\\r\\n TotalBandwidth = sum(SentBytes + ReceivedBytes)\\r\\n| extend \\r\\n TotalSentMB = round(TotalSent / 1024.0 / 1024.0, 2),\\r\\n TotalReceivedMB = round(TotalReceived / 1024.0 / 1024.0, 2),\\r\\n TotalBandwidthMB = round(TotalBandwidth / 1024.0 / 1024.0, 2)\\r\\n| project \\r\\n TotalSentMB,\\r\\n TotalReceivedMB,\\r\\n TotalBandwidthMB\\r\\n| extend dummy = 1\\r\\n| project-away dummy\\r\\n| mv-expand \\r\\n Column = pack_array(\\\"TotalSentMB\\\", \\\"TotalReceivedMB\\\", \\\"TotalBandwidthMB\\\"),\\r\\n Value = pack_array(TotalSentMB, TotalReceivedMB, TotalBandwidthMB)\\r\\n| extend \\r\\n snapshot = case(\\r\\n Column == \\\"TotalSentMB\\\", \\\"Total Sent (MB)\\\",\\r\\n Column == \\\"TotalReceivedMB\\\", \\\"Total Received (MB)\\\",\\r\\n Column == \\\"TotalBandwidthMB\\\", \\\"Total Bandwidth (MB)\\\",\\r\\n \\\"Unknown\\\"\\r\\n ),\\r\\n col1 = iff(Value < 0.01, 0.00, Value)\\r\\n| project-away Column, Value\\r\\n| order by snapshot asc\",\"size\":4,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"snapshot\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"col1\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"maximumFractionDigits\":2,\"maximumSignificantDigits\":3}}},\"showBorder\":true}},\"name\":\"query - 17\"},{\"type\":1,\"content\":{\"json\":\"# Traffic Logs (filtered)\"},\"name\":\"text - 12\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| where DestinationFqdn contains \\\"{URL}\\\"\\r\\n| project \\r\\n Timestamp = TimeGenerated,\\r\\n User = UserPrincipalName,\\r\\n [\\\"Source IP\\\"] = SourceIp,\\r\\n [\\\"Destination IP\\\"] = DestinationIp,\\r\\n [\\\"Destination Port\\\"] = DestinationPort,\\r\\n [\\\"Destination FQDN\\\"] = DestinationFqdn,\\r\\n Action = case(\\r\\n tolower(Action) == \\\"allow\\\", \\\"šŸŸ¢ Allow\\\", \\r\\n tolower(Action) == \\\"block\\\", \\\"šŸ”“ Block\\\", \\r\\n tolower(Action) // This returns the action in lowercase if it doesn't match \\\"allow\\\" or \\\"block\\\"\\r\\n ),\\r\\n [\\\"Policy Name\\\"] = PolicyName,\\r\\n [\\\"Policy Rule ID\\\"] = PolicyRuleId,\\r\\n [\\\"Received Bytes\\\"] = ReceivedBytes,\\r\\n [\\\"Sent Bytes\\\"] = SentBytes,\\r\\n [\\\"Device OS\\\"] = DeviceOperatingSystem \\r\\n| order by Timestamp desc\\r\\n\",\"size\":0,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"name\":\"query - 13\"}]},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"URLLookup\"},\"name\":\"group - URL Lookup\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| project \\r\\n Timestamp = TimeGenerated,\\r\\n User = UserPrincipalName,\\r\\n [\\\"Source IP\\\"] = SourceIp,\\r\\n [\\\"Destination IP\\\"] = DestinationIp,\\r\\n [\\\"Destination Port\\\"] = DestinationPort,\\r\\n [\\\"Destination FQDN\\\"] = DestinationFqdn,\\r\\n Action = case(\\r\\n tolower(Action) == \\\"allow\\\", \\\"šŸŸ¢ Allow\\\", \\r\\n tolower(Action) == \\\"block\\\", \\\"šŸ”“ Block\\\", \\r\\n tolower(Action) // This returns the action in lowercase if it doesn't match \\\"allow\\\" or \\\"block\\\"\\r\\n ),\\r\\n [\\\"Policy Name\\\"] = PolicyName,\\r\\n [\\\"Web Category\\\"] = DestinationWebCategories,\\r\\n [\\\"Transport Protocol\\\"] = TransportProtocol,\\r\\n [\\\"Traffic Type\\\"] = TrafficType,\\r\\n [\\\"Received Bytes\\\"] = ReceivedBytes,\\r\\n [\\\"Sent Bytes\\\"] = SentBytes,\\r\\n [\\\"Device OS\\\"] = DeviceOperatingSystem,\\r\\n [\\\"Policy Rule ID\\\"] = PolicyRuleId\\r\\n| order by Timestamp desc\\r\\n\",\"size\":3,\"title\":\"Traffic Logs\",\"timeContextFromParameter\":\"TimeRange\",\"showRefreshButton\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"rowLimit\":1000,\"filter\":true}},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"AllTraffic\"},\"name\":\"query - 6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"// Unique Users\\nNetworkAccessTraffic\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Extend each row with geolocation info\\n| project SourceIp, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\\n| summarize UniqueUsers=dcount(Country)\\n| extend snapshot = \\\"Total Locations\\\"\\n| project col1 = UniqueUsers, snapshot\\n\\n// Union with Unique Devices\\n| union (\\n NetworkAccessTraffic\\n | where UserPrincipalName in ({Users}) or '*' in ({Users})\\n | extend BytesInGB = todouble(SentBytes + ReceivedBytes) / (1024 * 1024 * 1024) // Convert bytes to gigabytes\\n | summarize TotalBytesGB = sum(BytesInGB)\\n | extend snapshot = \\\"Total Bytes (GB)\\\"\\n | project col1 = tolong(TotalBytesGB), snapshot\\n)\\n\\n// Union with Total Internet Access\\n| union (\\n NetworkAccessTraffic\\n | where UserPrincipalName in ({Users}) or '*' in ({Users})\\n | summarize TotalTransactions = count()\\n | extend snapshot = \\\"Total Transactions\\\"\\n | project col1 = TotalTransactions, snapshot\\n)\\n\\n// Union with Total Private Access\\n// Order by Snapshot for consistent tile ordering on dashboard\\n| order by snapshot\",\"size\":4,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"snapshot\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"col1\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"}},\"showBorder\":true,\"size\":\"auto\"},\"mapSettings\":{\"locInfo\":\"LatLong\",\"sizeSettings\":\"ExistingClients\",\"sizeAggregation\":\"Sum\",\"legendMetric\":\"ExistingClients\",\"legendAggregation\":\"Sum\",\"itemColorSettings\":{\"type\":\"heatmap\",\"colorAggregation\":\"Sum\",\"nodeColorField\":\"ExistingClients\",\"heatmapPalette\":\"greenRed\"}},\"textSettings\":{\"style\":\"bignumber\"}},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 2\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| extend BytesIn = todouble(SentBytes + ReceivedBytes) / (1024 * 1024) // Convert bytes to Mbytes\\r\\n| summarize TotalBytesMB = sum(BytesIn) by bin(TimeGenerated, 1h), TrafficType\\r\\n| order by bin(TimeGenerated, 1h) asc, TrafficType asc\\r\\n| project TimeGenerated, TrafficType, TotalBytesMB\\r\\n\",\"size\":2,\"title\":\"Usage Over Time (MB)\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"barchart\"},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 0\"}]},\"name\":\"group - 5\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Extend each row with geolocation info\\r\\n| project TimeGenerated, SourceIp, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\\r\\n| where Country != \\\"\\\"\\r\\n| summarize Count = count() by City, State, Country\\r\\n\\r\\n\",\"size\":3,\"title\":\"Locations\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"Country\",\"latitude\":\"Latitude\",\"longitude\":\"Longitude\",\"sizeSettings\":\"Count\",\"sizeAggregation\":\"Sum\",\"labelSettings\":\"Country\",\"legendMetric\":\"Country\",\"legendAggregation\":\"Count\",\"itemColorSettings\":{\"nodeColorField\":\"Count\",\"colorAggregation\":\"Sum\",\"type\":\"heatmap\",\"heatmapPalette\":\"turquoise\"}}},\"name\":\"query - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| where tolower(Action) == \\\"allow\\\" and DestinationWebCategories != '' // Filter for allowed traffic\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| extend firstCategory = tostring(split(DestinationWebCategories, ',')[0]) // Split and get the first category\\r\\n| summarize Count = count() by firstCategory\\r\\n| top 10 by Count\\r\\n\",\"size\":2,\"title\":\"Top Allowed Web Categories\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"piechart\"},\"customWidth\":\"50\",\"name\":\"query - 7\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| where tolower(Action) == \\\"block\\\" and DestinationWebCategories != '' // Filter for blocked traffic\\r\\n| extend firstCategory = tostring(split(DestinationWebCategories, ',')[0]) // Split and get the first category\\r\\n| summarize Count = count() by firstCategory\\r\\n| top 10 by Count\\r\\n\",\"size\":3,\"title\":\"Top Blocked Web Categories\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"piechart\"},\"customWidth\":\"50\",\"name\":\"query - 6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic \\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| where tolower(Action) == \\\"block\\\" and DestinationFqdn != '' // Filter for blocked traffic with non-empty Destination FQDN\\r\\n| summarize Count = count() by [\\\"Destination FQDN\\\"] = DestinationFqdn, [\\\"Destination Web Categories\\\"] = DestinationWebCategories, [\\\"Policy Name\\\"] = PolicyName\\r\\n| order by Count\\r\\n\",\"size\":0,\"title\":\"Top Blocked Destinations\",\"timeContextFromParameter\":\"TimeRange\",\"showRefreshButton\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Count\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\"}}],\"rowLimit\":1000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"$gen_bar_Count_3\",\"sortOrder\":2}]},\"sortBy\":[{\"itemKey\":\"$gen_bar_Count_3\",\"sortOrder\":2}]},\"customWidth\":\"50\",\"name\":\"query - 5\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| where SentBytes > 0\\r\\n| where tolower(Action) != \\\"block\\\"\\r\\n| summarize \\r\\n Count = count(), \\r\\n [\\\"Sent Bytes\\\"] = sum(SentBytes), \\r\\n [\\\"Received Bytes\\\"] = sum(ReceivedBytes), \\r\\n [\\\"Total Bytes\\\"] = sum(ReceivedBytes + SentBytes) \\r\\n by [\\\"Destination FQDN\\\"] = DestinationFqdn\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Top Allowed Destinations\",\"timeContextFromParameter\":\"TimeRange\",\"showRefreshButton\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Count\",\"formatter\":4,\"formatOptions\":{\"palette\":\"magenta\"}},{\"columnMatch\":\"Recived\",\"formatter\":4,\"formatOptions\":{\"palette\":\"turquoise\"}},{\"columnMatch\":\"Total\",\"formatter\":4,\"formatOptions\":{\"palette\":\"pink\"}},{\"columnMatch\":\"Sent\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\"}}],\"rowLimit\":1000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"$gen_bar_Count_1\",\"sortOrder\":2}]},\"sortBy\":[{\"itemKey\":\"$gen_bar_Count_1\",\"sortOrder\":2}]},\"customWidth\":\"50\",\"name\":\"query - 1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where TransportProtocol != ''\\r\\n| summarize Count = count() by toupper(TransportProtocol)\\r\\n| top 10 by Count\\r\\n\",\"size\":2,\"title\":\"Protocol Distribution\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"piechart\"},\"customWidth\":\"50\",\"name\":\"query - 3\"}]},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"group - 4\"}],\"fallbackResourceIds\":[\"Global Secure Access\"],\"fromTemplateId\":\"GSA Network Traffic Insights\",\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", - "version": "1.0", - "sourceId": "[variables('workspaceResourceId')]", - "category": "sentinel" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('Workbook-', last(split(variables('workbookId2'),'/'))))]", - "properties": { - "description": "@{workbookKey=GSANetworkTraffic; logoFileName=gsa.svg; description=This workbook provides an overview of all traffic logs within your network, offering insights into data transfer, anomalies, and potential threats.; dataTypesDependencies=System.Object[]; dataConnectorsDependencies=System.Object[]; previewImagesFileNames=System.Object[]; version=1.0.1; title=Microsoft Global Secure Access Traffic Logs; templateRelativePath=GSANetworkTraffic.json; subtitle=; provider=Microsoft}.description", - "parentId": "[variables('workbookId2')]", - "contentId": "[variables('_workbookContentId2')]", - "kind": "Workbook", - "version": "[variables('workbookVersion2')]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - }, - "dependencies": { - "operator": "AND", - "criteria": [ - { - "contentId": "AzureActiveDirectory", - "kind": "DataConnector" - } - ] - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('_workbookContentId2')]", - "contentKind": "Workbook", - "displayName": "[parameters('workbook2-name')]", - "contentProductId": "[variables('_workbookcontentProductId2')]", - "id": "[variables('_workbookcontentProductId2')]", - "version": "[variables('workbookVersion2')]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject1').analyticRuleTemplateSpecName1]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "Identity - AfterHoursActivity_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject1').analyticRuleVersion1]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject1')._analyticRulecontentId1]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "This query identifies connections that occur outside of the defined operational hours. It helps in monitoring and flagging any unusual activity that may occur during non-business hours, indicating potential security concerns or policy violations.", - "displayName": "Detect Connections Outside Operational Hours", - "enabled": false, - "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet operational_start_hour = 8; // Start of operational hours (8 AM)\nlet operational_end_hour = 18; // End of operational hours (6 PM)\nNetworkAccessTraffic\n| where TimeGenerated between(starttime .. endtime)\n| extend HourOfDay = datetime_part('hour', TimeGenerated)\n| where HourOfDay < operational_start_hour or HourOfDay >= operational_end_hour\n| project TimeGenerated, UserPrincipalName, SourceIp, DestinationIp, DestinationPort, Action, DeviceId, DeviceOperatingSystem, ConnectionId\n| extend IPCustomEntity = SourceIp, AccountCustomEntity = UserPrincipalName\n", - "queryFrequency": "PT1H", - "queryPeriod": "PT24H", - "severity": "High", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 0, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" - } - ], - "tactics": [ - "InitialAccess" - ], - "techniques": [ - "T1078", - "T1133" - ], - "entityMappings": [ - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "AccountCustomEntity", - "identifier": "Name" - } - ] - }, - { - "entityType": "IP", - "fieldMappings": [ - { - "columnName": "IPCustomEntity", - "identifier": "Address" - } - ] - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject1').analyticRuleId1,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 1", - "parentId": "[variables('analyticRuleObject1').analyticRuleId1]", - "contentId": "[variables('analyticRuleObject1')._analyticRulecontentId1]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject1').analyticRuleVersion1]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject1')._analyticRulecontentId1]", - "contentKind": "AnalyticsRule", - "displayName": "Detect Connections Outside Operational Hours", - "contentProductId": "[variables('analyticRuleObject1')._analyticRulecontentProductId1]", - "id": "[variables('analyticRuleObject1')._analyticRulecontentProductId1]", - "version": "[variables('analyticRuleObject1').analyticRuleVersion1]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject2').analyticRuleTemplateSpecName2]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "Identity - SharedSessions_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject2').analyticRuleVersion2]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject2')._analyticRulecontentId2]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "This query identifies network sessions based on DeviceId and UserPrincipalName, then checks for changed IP addresses and overlapping session times.", - "displayName": "Detect IP Address Changes and Overlapping Sessions", - "enabled": false, - "query": "// Identify sessions\nlet sessions = \n NetworkAccessTraffic\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), SourceIps = make_set(SourceIp) by DeviceId, UserPrincipalName, SessionId\n | sort by StartTime asc;\n// Check for changed IP addresses and overlapping session times\nsessions\n | extend PreviousSourceIps = prev(SourceIps, 1)\n | extend PreviousEndTime = prev(EndTime, 1)\n | extend PreviousDeviceId = prev(DeviceId, 1)\n | extend PreviousUserPrincipalName = prev(UserPrincipalName, 1)\n | where DeviceId == PreviousDeviceId and UserPrincipalName == PreviousUserPrincipalName\n | where set_difference(SourceIps, PreviousSourceIps) != dynamic([]) // Check if the current and previous IP sets differ\n | where PreviousEndTime > StartTime // Check for overlapping session times\n | project DeviceId, UserPrincipalName, SourceIps, PreviousSourceIps, StartTime, EndTime, PreviousEndTime\n | extend IPCustomEntity = tostring(array_slice(SourceIps, 0, 1)[0]), PreviousIPCustomEntity = tostring(array_slice(PreviousSourceIps, 0, 1)[0]), AccountCustomEntity = UserPrincipalName\n", - "queryFrequency": "PT1H", - "queryPeriod": "PT24H", - "severity": "High", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 0, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" - } - ], - "tactics": [ - "InitialAccess" - ], - "techniques": [ - "T1078", - "T1133" - ], - "entityMappings": [ - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "AccountCustomEntity", - "identifier": "Name" - } - ] - }, - { - "entityType": "IP", - "fieldMappings": [ - { - "columnName": "IPCustomEntity", - "identifier": "Address" - } - ] - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject2').analyticRuleId2,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 2", - "parentId": "[variables('analyticRuleObject2').analyticRuleId2]", - "contentId": "[variables('analyticRuleObject2')._analyticRulecontentId2]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject2').analyticRuleVersion2]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject2')._analyticRulecontentId2]", - "contentKind": "AnalyticsRule", - "displayName": "Detect IP Address Changes and Overlapping Sessions", - "contentProductId": "[variables('analyticRuleObject2')._analyticRulecontentProductId2]", - "id": "[variables('analyticRuleObject2')._analyticRulecontentProductId2]", - "version": "[variables('analyticRuleObject2').analyticRuleVersion2]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject3').analyticRuleTemplateSpecName3]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "Office 365 - exchange_auditlogdisabled_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject3').analyticRuleVersion3]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject3')._analyticRulecontentId3]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "Identifies when the Exchange audit logging has been disabled, which may indicate an adversary attempt to evade detection or bypass other defenses.", - "displayName": "GSA Enriched Office 365 - Exchange AuditLog Disabled", - "enabled": false, - "query": "// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"Exchange\" \n | where UserType in~ (\"Admin\", \"DcAdmin\")\n // Only admin or global-admin can disable audit logging\n | where Operation =~ \"Set-AdminAuditLogConfig\"\n | extend ParsedParameters = parse_json(Parameters)\n | extend AdminAuditLogEnabledValue = tostring(ParsedParameters[3].Value)\n | where AdminAuditLogEnabledValue =~ \"False\"\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() \n by Operation, UserType, UserId, ClientIP, ResultStatus, Parameters, AdminAuditLogEnabledValue\n | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), \n iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[1]), UserId))\n | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '')\n | extend AccountNTDomain = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[0]), '');\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"Exchange\"\n | where UserType in~ (\"Admin\", \"DcAdmin\")\n | where Operation =~ \"Set-AdminAuditLogConfig\"\n | extend ParsedParameters = parse_json(AdditionalProperties.Parameters)\n | extend AdminAuditLogEnabledValue = tostring(ParsedParameters[3].Value)\n | where AdminAuditLogEnabledValue =~ \"False\"\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() \n by Operation, UserType, UserId, ClientIP = SourceIp, ResultStatus, Parameters = tostring(AdditionalProperties.Parameters), AdminAuditLogEnabledValue\n | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), \n iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[1]), UserId))\n | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '')\n | extend AccountNTDomain = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[0]), '');\n// Combine Office and Enriched Events and Deduplicate\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTimeUtc, *) by Operation, UserId, ClientIP;\n// Project Final Output\nCombinedEvents\n | project StartTimeUtc, EndTimeUtc, Operation, UserType, UserId, ClientIP, ResultStatus, Parameters, AdminAuditLogEnabledValue, AccountName, AccountUPNSuffix, AccountNTDomain\n", - "queryFrequency": "P1D", - "queryPeriod": "P1D", - "severity": "Medium", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 0, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" - }, - { - "dataTypes": [ - "OfficeActivity (Exchange)" - ], - "connectorId": "Office365" - } - ], - "tactics": [ - "DefenseEvasion" - ], - "techniques": [ - "T1562" - ], - "entityMappings": [ - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "UserId", - "identifier": "FullName" - }, - { - "columnName": "AccountName", - "identifier": "Name" - }, - { - "columnName": "AccountUPNSuffix", - "identifier": "UPNSuffix" - } - ] - }, - { - "entityType": "IP", - "fieldMappings": [ - { - "columnName": "ClientIP", - "identifier": "Address" - } - ] - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject3').analyticRuleId3,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 3", - "parentId": "[variables('analyticRuleObject3').analyticRuleId3]", - "contentId": "[variables('analyticRuleObject3')._analyticRulecontentId3]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject3').analyticRuleVersion3]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject3')._analyticRulecontentId3]", - "contentKind": "AnalyticsRule", - "displayName": "GSA Enriched Office 365 - Exchange AuditLog Disabled", - "contentProductId": "[variables('analyticRuleObject3')._analyticRulecontentProductId3]", - "id": "[variables('analyticRuleObject3')._analyticRulecontentProductId3]", - "version": "[variables('analyticRuleObject3').analyticRuleVersion3]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject4').analyticRuleTemplateSpecName4]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "Office 365 - External User added to Team and immediately uploads file_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject4').analyticRuleVersion4]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject4')._analyticRulecontentId4]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "This detection identifies when an external user is added to a Team or Teams chat and shares a file which is accessed by many users (>10) and the users is removed within short period of time. This might be an indicator of suspicious activity.", - "displayName": "GSA Enriched Office 365 - Accessed files shared by temporary external user", - "enabled": false, - "query": "let fileAccessThreshold = 10;\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberAdded\"\n | extend MemberAdded = tostring(parse_json(Members)[0].UPN)\n | where MemberAdded contains \"#EXT#\"\n | project TimeAdded = TimeGenerated, Operation, MemberAdded, UserWhoAdded = UserId, TeamName\n | join kind=inner (\n OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberRemoved\"\n | extend MemberAdded = tostring(parse_json(Members)[0].UPN)\n | where MemberAdded contains \"#EXT#\"\n | project TimeDeleted = TimeGenerated, Operation, MemberAdded, UserWhoDeleted = UserId, TeamName\n ) on MemberAdded\n | where TimeDeleted > TimeAdded\n | join kind=inner (\n OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | where Operation == \"FileUploaded\"\n | extend MemberAdded = UserId\n | join kind=inner (\n OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where Operation == \"FileAccessed\"\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | summarize FileAccessCount = count() by OfficeObjectId\n | where FileAccessCount > fileAccessThreshold\n ) on OfficeObjectId\n ) on MemberAdded\n | project TimeAdded, TimeDeleted, MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberAdded\"\n | extend MemberAdded = tostring(parse_json(tostring(AdditionalProperties)).Members[0].UPN)\n | where MemberAdded contains \"#EXT#\"\n | project TimeAdded = TimeGenerated, Operation, MemberAdded, UserWhoAdded = UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n | join kind=inner (\n EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberRemoved\"\n | extend MemberAdded = tostring(parse_json(tostring(AdditionalProperties)).Members[0].UPN)\n | where MemberAdded contains \"#EXT#\"\n | project TimeDeleted = TimeGenerated, Operation, MemberAdded, UserWhoDeleted = UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n ) on MemberAdded, TeamName\n | where TimeDeleted > TimeAdded\n | join kind=inner (\n EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation == \"FileUploaded\"\n | extend MemberAdded = UserId, SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl), TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | join kind=inner (\n EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation == \"FileAccessed\"\n | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl), TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | summarize FileAccessCount = count() by ObjectId, TeamName\n | where FileAccessCount > fileAccessThreshold\n ) on ObjectId, TeamName\n ) on MemberAdded, TeamName\n | project TimeAdded, TimeDeleted, MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName;\n// Combine Office and Enriched Events and Deduplicate\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeAdded, *) by MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName;\n// Project Final Output\nCombinedEvents\n | extend MemberAddedAccountName = tostring(split(MemberAdded, \"@\")[0]), MemberAddedAccountUPNSuffix = tostring(split(MemberAdded, \"@\")[1])\n | extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, \"@\")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, \"@\")[1])\n | extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, \"@\")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, \"@\")[1])\n | project TimeAdded, TimeDeleted, MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName, MemberAddedAccountName, MemberAddedAccountUPNSuffix, UserWhoAddedAccountName, UserWhoAddedAccountUPNSuffix, UserWhoDeletedAccountName, UserWhoDeletedAccountUPNSuffix\n", - "queryFrequency": "PT1H", - "queryPeriod": "PT1H", - "severity": "Low", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 0, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" - }, - { - "dataTypes": [ - "OfficeActivity (Teams)" - ], - "connectorId": "Office365" - } - ], - "tactics": [ - "InitialAccess" - ], - "techniques": [ - "T1566" - ], - "entityMappings": [ - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "MemberAdded", - "identifier": "FullName" - }, - { - "columnName": "MemberAddedAccountName", - "identifier": "Name" - }, - { - "columnName": "MemberAddedAccountUPNSuffix", - "identifier": "UPNSuffix" - } - ] - }, - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "UserWhoAdded", - "identifier": "FullName" - }, - { - "columnName": "UserWhoAddedAccountName", - "identifier": "Name" - }, - { - "columnName": "UserWhoAddedAccountUPNSuffix", - "identifier": "UPNSuffix" - } - ] - }, - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "UserWhoDeleted", - "identifier": "FullName" - }, - { - "columnName": "UserWhoDeletedAccountName", - "identifier": "Name" - }, - { - "columnName": "UserWhoDeletedAccountUPNSuffix", - "identifier": "UPNSuffix" - } - ] - }, - { - "entityType": "IP", - "fieldMappings": [ - { - "columnName": "ClientIP", - "identifier": "Address" - } - ] - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject4').analyticRuleId4,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 4", - "parentId": "[variables('analyticRuleObject4').analyticRuleId4]", - "contentId": "[variables('analyticRuleObject4')._analyticRulecontentId4]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject4').analyticRuleVersion4]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject4')._analyticRulecontentId4]", - "contentKind": "AnalyticsRule", - "displayName": "GSA Enriched Office 365 - Accessed files shared by temporary external user", - "contentProductId": "[variables('analyticRuleObject4')._analyticRulecontentProductId4]", - "id": "[variables('analyticRuleObject4')._analyticRulecontentProductId4]", - "version": "[variables('analyticRuleObject4').analyticRuleVersion4]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject5').analyticRuleTemplateSpecName5]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "Office 365 - ExternalUserAddedRemovedInTeams_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject5').analyticRuleVersion5]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject5')._analyticRulecontentId5]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "This detection flags the occurrences of external user accounts that are added to a Team and then removed within one hour.", - "displayName": "GSA Enriched Office 365 - External User Added and Removed in Short Timeframe", - "enabled": false, - "query": "let TeamsAddDelOffice = (Op:string){\n OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation == Op\n | where Members has (\"#EXT#\")\n | mv-expand Members\n | extend UPN = tostring(Members.UPN)\n | where UPN has (\"#EXT#\")\n | project TimeGenerated, Operation, UPN, UserId, TeamName, ClientIP\n};\nlet TeamsAddDelEnriched = (Op:string){\n EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"MicrosoftTeams\"\n | where Operation == Op\n | where tostring(AdditionalProperties.Members) has (\"#EXT#\")\n | mv-expand Members = parse_json(tostring(AdditionalProperties.Members))\n | extend UPN = tostring(Members.UPN)\n | where UPN has (\"#EXT#\")\n | project TimeGenerated, Operation, UPN, UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName), ClientIP = SourceIp\n};\nlet TeamsAddOffice = TeamsAddDelOffice(\"MemberAdded\")\n | project TimeAdded = TimeGenerated, Operation, MemberAdded = UPN, UserWhoAdded = UserId, TeamName, ClientIP;\nlet TeamsDelOffice = TeamsAddDelOffice(\"MemberRemoved\")\n | project TimeDeleted = TimeGenerated, Operation, MemberRemoved = UPN, UserWhoDeleted = UserId, TeamName, ClientIP;\nlet TeamsAddEnriched = TeamsAddDelEnriched(\"MemberAdded\")\n | project TimeAdded = TimeGenerated, Operation, MemberAdded = UPN, UserWhoAdded = UserId, TeamName, ClientIP;\nlet TeamsDelEnriched = TeamsAddDelEnriched(\"MemberRemoved\")\n | project TimeDeleted = TimeGenerated, Operation, MemberRemoved = UPN, UserWhoDeleted = UserId, TeamName, ClientIP;\nlet TeamsAdd = TeamsAddOffice\n | union TeamsAddEnriched\n | project TimeAdded, Operation, MemberAdded, UserWhoAdded, TeamName, ClientIP;\nlet TeamsDel = TeamsDelOffice\n | union TeamsDelEnriched\n | project TimeDeleted, Operation, MemberRemoved, UserWhoDeleted, TeamName, ClientIP;\nTeamsAdd\n| join kind=inner (TeamsDel) on $left.MemberAdded == $right.MemberRemoved\n| where TimeDeleted > TimeAdded\n| project TimeAdded, TimeDeleted, MemberAdded_Removed = MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName, ClientIP\n| extend MemberAdded_RemovedAccountName = tostring(split(MemberAdded_Removed, \"@\")[0]), MemberAdded_RemovedAccountUPNSuffix = tostring(split(MemberAdded_Removed, \"@\")[1])\n| extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, \"@\")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, \"@\")[1])\n| extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, \"@\")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, \"@\")[1])\n", - "queryFrequency": "PT1H", - "queryPeriod": "PT1H", - "severity": "Low", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 0, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" - }, - { - "dataTypes": [ - "OfficeActivity (Teams)" - ], - "connectorId": "Office365" - } - ], - "tactics": [ - "Persistence" - ], - "techniques": [ - "T1136" - ], - "entityMappings": [ - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "MemberAdded_Removed", - "identifier": "FullName" - }, - { - "columnName": "MemberAdded_RemovedAccountName", - "identifier": "Name" - }, - { - "columnName": "MemberAdded_RemovedAccountUPNSuffix", - "identifier": "UPNSuffix" - } - ] - }, - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "UserWhoAdded", - "identifier": "FullName" - }, - { - "columnName": "UserWhoAddedAccountName", - "identifier": "Name" - }, - { - "columnName": "UserWhoAddedAccountUPNSuffix", - "identifier": "UPNSuffix" - } - ] - }, - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "UserWhoDeleted", - "identifier": "FullName" - }, - { - "columnName": "UserWhoDeletedAccountName", - "identifier": "Name" - }, - { - "columnName": "UserWhoDeletedAccountUPNSuffix", - "identifier": "UPNSuffix" - } - ] - }, - { - "entityType": "IP", - "fieldMappings": [ - { - "columnName": "ClientIP", - "identifier": "Address" - } - ] - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject5').analyticRuleId5,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 5", - "parentId": "[variables('analyticRuleObject5').analyticRuleId5]", - "contentId": "[variables('analyticRuleObject5')._analyticRulecontentId5]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject5').analyticRuleVersion5]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject5')._analyticRulecontentId5]", - "contentKind": "AnalyticsRule", - "displayName": "GSA Enriched Office 365 - External User Added and Removed in Short Timeframe", - "contentProductId": "[variables('analyticRuleObject5')._analyticRulecontentProductId5]", - "id": "[variables('analyticRuleObject5')._analyticRulecontentProductId5]", - "version": "[variables('analyticRuleObject5').analyticRuleVersion5]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject6').analyticRuleTemplateSpecName6]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "Office 365 - Mail_redirect_via_ExO_transport_rule_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject6').analyticRuleVersion6]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject6')._analyticRulecontentId6]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "Identifies when an Exchange Online transport rule is configured to forward emails.\nThis could indicate an adversary mailbox configured to collect mail from multiple user accounts.", - "displayName": "GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule", - "enabled": false, - "query": "let officeActivityQuery = OfficeActivity\n | where OfficeWorkload == \"Exchange\"\n | where Operation in~ (\"New-TransportRule\", \"Set-TransportRule\")\n | mv-apply DynamicParameters = todynamic(Parameters) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | extend RuleName = case(\n Operation =~ \"Set-TransportRule\", OfficeObjectId,\n Operation =~ \"New-TransportRule\", ParsedParameters.Name,\n \"Unknown\"\n )\n | mv-expand ExpandedParameters = todynamic(Parameters)\n | where ExpandedParameters.Name in~ (\"BlindCopyTo\", \"RedirectMessageTo\") and isnotempty(ExpandedParameters.Value)\n | extend RedirectTo = ExpandedParameters.Value\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIP)[0]\n | extend From = ParsedParameters.From\n | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters\n | extend AccountName = tostring(split(UserId, \"@\")[0]), \n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\nlet enrichedLogsQuery = EnrichedMicrosoft365AuditLogs\n | where Workload == \"Exchange\"\n | where Operation in~ (\"New-TransportRule\", \"Set-TransportRule\")\n | mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | extend RuleName = case(\n Operation =~ \"Set-TransportRule\", ObjectId, // Assuming ObjectId maps to OfficeObjectId\n Operation =~ \"New-TransportRule\", ParsedParameters.Name,\n \"Unknown\"\n )\n | mv-expand ExpandedParameters = todynamic(tostring(AdditionalProperties.Parameters))\n | where ExpandedParameters.Name in~ (\"BlindCopyTo\", \"RedirectMessageTo\") and isnotempty(ExpandedParameters.Value)\n | extend RedirectTo = ExpandedParameters.Value\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIp)[0]\n | extend From = ParsedParameters.From\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters = tostring(AdditionalProperties.Parameters), UserAgent\n | extend AccountName = tostring(split(UserId, \"@\")[0]), \n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Combine both queries\nunion isfuzzy=true officeActivityQuery, enrichedLogsQuery\n| summarize arg_min(TimeGenerated, *) by RuleName, RedirectTo\n| project TimeGenerated, RedirectTo, IPAddress, Port, UserId, From, Operation, RuleName, Parameters, AccountName, AccountUPNSuffix\n| order by TimeGenerated desc;\n", - "queryFrequency": "PT1H", - "queryPeriod": "PT1H", - "severity": "Medium", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 0, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" - }, - { - "dataTypes": [ - "OfficeActivity (Exchange)" - ], - "connectorId": "Office365" - } - ], - "tactics": [ - "Collection", - "Exfiltration" - ], - "techniques": [ - "T1114", - "T1020" - ], - "entityMappings": [ - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "UserId", - "identifier": "FullName" - }, - { - "columnName": "AccountName", - "identifier": "Name" - }, - { - "columnName": "AccountUPNSuffix", - "identifier": "UPNSuffix" - } - ] - }, - { - "entityType": "IP", - "fieldMappings": [ - { - "columnName": "IPAddress", - "identifier": "Address" - } - ] - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject6').analyticRuleId6,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 6", - "parentId": "[variables('analyticRuleObject6').analyticRuleId6]", - "contentId": "[variables('analyticRuleObject6')._analyticRulecontentId6]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject6').analyticRuleVersion6]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject6')._analyticRulecontentId6]", - "contentKind": "AnalyticsRule", - "displayName": "GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule", - "contentProductId": "[variables('analyticRuleObject6')._analyticRulecontentProductId6]", - "id": "[variables('analyticRuleObject6')._analyticRulecontentProductId6]", - "version": "[variables('analyticRuleObject6').analyticRuleVersion6]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject7').analyticRuleTemplateSpecName7]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "Office 365 - Malicious_Inbox_Rule_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject7').analyticRuleVersion7]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject7')._analyticRulecontentId7]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "Often times after the initial compromise the attackers create inbox rules to delete emails that contain certain keywords.\nThis is done so as to limit ability to warn compromised users that they've been compromised. Below is a sample query that tries to detect this.\nReference: https://www.reddit.com/r/sysadmin/comments/7kyp0a/recent_phishing_attempts_my_experience_and_what/", - "displayName": "GSA Enriched Office 365 - Malicious Inbox Rule", - "enabled": false, - "query": "let Keywords = dynamic([\"helpdesk\", \"alert\", \"suspicious\", \"fake\", \"malicious\", \"phishing\", \"spam\", \"do not click\", \"do not open\", \"hijacked\", \"Fatal\"]);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"Exchange\" \n | where Operation =~ \"New-InboxRule\" and (ResultStatus =~ \"True\" or ResultStatus =~ \"Succeeded\")\n | where Parameters has \"Deleted Items\" or Parameters has \"Junk Email\" or Parameters has \"DeleteMessage\"\n | extend Events = todynamic(Parameters)\n | parse Events with * \"SubjectContainsWords\" SubjectContainsWords '}'*\n | parse Events with * \"BodyContainsWords\" BodyContainsWords '}'*\n | parse Events with * \"SubjectOrBodyContainsWords\" SubjectOrBodyContainsWords '}'*\n | where SubjectContainsWords has_any (Keywords)\n or BodyContainsWords has_any (Keywords)\n or SubjectOrBodyContainsWords has_any (Keywords)\n | extend ClientIPAddress = case(\n ClientIP has \".\", tostring(split(ClientIP, \":\")[0]),\n ClientIP has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIP, \"]\")[0]))),\n ClientIP\n )\n | extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords))\n | extend RuleDetail = case(\n OfficeObjectId contains '/', tostring(split(OfficeObjectId, '/')[-1]),\n OfficeObjectId contains '\\\\', tostring(split(OfficeObjectId, '\\\\')[-1]),\n \"Unknown\"\n )\n | summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Operation, UserId, ClientIPAddress, ResultStatus, Keyword, OriginatingServer, OfficeObjectId, RuleDetail\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend OriginatingServerName = tostring(split(OriginatingServer, \" \")[0]);\n\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"Exchange\"\n | where Operation =~ \"New-InboxRule\" and (ResultStatus =~ \"True\" or ResultStatus =~ \"Succeeded\")\n | where tostring(parse_json(tostring(AdditionalProperties)).Parameters) has \"Deleted Items\" \n or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has \"Junk Email\" \n or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has \"DeleteMessage\"\n | extend Events = parse_json(tostring(AdditionalProperties)).Parameters\n | extend SubjectContainsWords = tostring(Events.SubjectContainsWords), \n BodyContainsWords = tostring(Events.BodyContainsWords), \n SubjectOrBodyContainsWords = tostring(Events.SubjectOrBodyContainsWords)\n | where SubjectContainsWords has_any (Keywords) \n or BodyContainsWords has_any (Keywords) \n or SubjectOrBodyContainsWords has_any (Keywords)\n | extend ClientIPAddress = case(\n ClientIp has \".\", tostring(split(ClientIp, \":\")[0]),\n ClientIp has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIp, \"]\")[0]))),\n ClientIp\n )\n | extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords))\n | extend RuleDetail = case(\n ObjectId contains '/', tostring(split(ObjectId, '/')[-1]),\n ObjectId contains '\\\\', tostring(split(ObjectId, '\\\\')[-1]),\n \"Unknown\"\n )\n | summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Operation, UserId, ClientIPAddress, ResultStatus, Keyword, ObjectId, RuleDetail\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Combine and Deduplicate\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTimeUtc, *) by Operation, UserId, ClientIPAddress;\n\n// Final Output\nCombinedEvents\n | project StartTimeUtc, EndTimeUtc, Operation, UserId, ClientIPAddress, ResultStatus, Keyword, RuleDetail, AccountName, AccountUPNSuffix;\n", - "queryFrequency": "P1D", - "queryPeriod": "P1D", - "severity": "Medium", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 0, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" - }, - { - "dataTypes": [ - "OfficeActivity (Exchange)" - ], - "connectorId": "Office365" - } - ], - "tactics": [ - "Persistence", - "DefenseEvasion" - ], - "techniques": [ - "T1098", - "T1078" - ], - "entityMappings": [ - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "UserId", - "identifier": "FullName" - }, - { - "columnName": "AccountName", - "identifier": "Name" - }, - { - "columnName": "AccountUPNSuffix", - "identifier": "UPNSuffix" - } - ] - }, - { - "entityType": "IP", - "fieldMappings": [ - { - "columnName": "ClientIPAddress", - "identifier": "Address" - } - ] - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject7').analyticRuleId7,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 7", - "parentId": "[variables('analyticRuleObject7').analyticRuleId7]", - "contentId": "[variables('analyticRuleObject7')._analyticRulecontentId7]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject7').analyticRuleVersion7]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject7')._analyticRulecontentId7]", - "contentKind": "AnalyticsRule", - "displayName": "GSA Enriched Office 365 - Malicious Inbox Rule", - "contentProductId": "[variables('analyticRuleObject7')._analyticRulecontentProductId7]", - "id": "[variables('analyticRuleObject7')._analyticRulecontentProductId7]", - "version": "[variables('analyticRuleObject7').analyticRuleVersion7]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject8').analyticRuleTemplateSpecName8]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "Office 365 - MultipleTeamsDeletes_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject8').analyticRuleVersion8]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject8')._analyticRulecontentId8]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "This detection flags the occurrences of deleting multiple teams within a day.\nThis data is a part of Office 365 Connector in Microsoft Sentinel.", - "displayName": "GSA Enriched Office 365 - Multiple Teams deleted by a single user", - "enabled": false, - "query": "// Set the maximum number of deleted teams to flag suspicious activity\nlet max_delete_count = 3;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"MicrosoftTeams\"\n | where Operation =~ \"TeamDeleted\"\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DeletedTeams = make_set(tostring(AdditionalProperties.TeamName), 1000) by UserId\n | where array_length(DeletedTeams) > max_delete_count\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"TeamDeleted\"\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DeletedTeams = make_set(TeamName, 1000) by UserId\n | where array_length(DeletedTeams) > max_delete_count\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// Combine and Deduplicate Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by UserId;\n// Final Output\nCombinedEvents\n | project StartTime, EndTime, DeletedTeams, UserId, AccountName, AccountUPNSuffix\n", - "queryFrequency": "P1D", - "queryPeriod": "P1D", - "severity": "Low", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 0, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" - }, - { - "dataTypes": [ - "OfficeActivity (Teams)" - ], - "connectorId": "Office365" - } - ], - "tactics": [ - "Impact" - ], - "techniques": [ - "T1485", - "T1489" - ], - "entityMappings": [ - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "UserId", - "identifier": "FullName" - }, - { - "columnName": "AccountName", - "identifier": "Name" - }, - { - "columnName": "AccountUPNSuffix", - "identifier": "UPNSuffix" - } - ] - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject8').analyticRuleId8,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 8", - "parentId": "[variables('analyticRuleObject8').analyticRuleId8]", - "contentId": "[variables('analyticRuleObject8')._analyticRulecontentId8]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject8').analyticRuleVersion8]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject8')._analyticRulecontentId8]", - "contentKind": "AnalyticsRule", - "displayName": "GSA Enriched Office 365 - Multiple Teams deleted by a single user", - "contentProductId": "[variables('analyticRuleObject8')._analyticRulecontentProductId8]", - "id": "[variables('analyticRuleObject8')._analyticRulecontentProductId8]", - "version": "[variables('analyticRuleObject8').analyticRuleVersion8]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject9').analyticRuleTemplateSpecName9]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "Office 365 - Office_MailForwarding_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject9').analyticRuleVersion9]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject9')._analyticRulecontentId9]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "Identifies when multiple (more than one) users' mailboxes are configured to forward to the same destination. \nThis could be an attacker-controlled destination mailbox configured to collect mail from multiple compromised user accounts.", - "displayName": "GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination", - "enabled": false, - "query": "// Set query parameters\nlet queryfrequency = 1d;\nlet queryperiod = 7d;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated > ago(queryperiod)\n | where Workload =~ \"Exchange\"\n // Uncomment or adjust the following line based on actual field usage\n // | where Operation in (\"Set-Mailbox\", \"New-InboxRule\", \"Set-InboxRule\")\n | where tostring(AdditionalProperties.Parameters) has_any (\"ForwardTo\", \"RedirectTo\", \"ForwardingSmtpAddress\")\n | mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source')\n | extend DestinationMailAddress = tolower(case(\n isnotempty(column_ifexists(\"ForwardTo\", \"\")), column_ifexists(\"ForwardTo\", \"\"),\n isnotempty(column_ifexists(\"RedirectTo\", \"\")), column_ifexists(\"RedirectTo\", \"\"),\n isnotempty(column_ifexists(\"ForwardingSmtpAddress\", \"\")), trim_start(@\"smtp:\", column_ifexists(\"ForwardingSmtpAddress\", \"\")),\n \"\"))\n | where isnotempty(DestinationMailAddress)\n | mv-expand split(DestinationMailAddress, \";\")\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIp)[0]\n | extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1])\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP\n | where DistinctUserCount > 1 and EndTime > ago(queryfrequency)\n | mv-expand UserId to typeof(string)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated > ago(queryperiod)\n | where OfficeWorkload =~ \"Exchange\"\n // Uncomment or adjust the following line based on actual field usage\n // | where Operation in (\"Set-Mailbox\", \"New-InboxRule\", \"Set-InboxRule\")\n | where Parameters has_any (\"ForwardTo\", \"RedirectTo\", \"ForwardingSmtpAddress\")\n | mv-apply DynamicParameters = todynamic(Parameters) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source')\n | extend DestinationMailAddress = tolower(case(\n isnotempty(column_ifexists(\"ForwardTo\", \"\")), column_ifexists(\"ForwardTo\", \"\"),\n isnotempty(column_ifexists(\"RedirectTo\", \"\")), column_ifexists(\"RedirectTo\", \"\"),\n isnotempty(column_ifexists(\"ForwardingSmtpAddress\", \"\")), trim_start(@\"smtp:\", column_ifexists(\"ForwardingSmtpAddress\", \"\")),\n \"\"))\n | where isnotempty(DestinationMailAddress)\n | mv-expand split(DestinationMailAddress, \";\")\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIP)[0]\n | extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1])\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP\n | where DistinctUserCount > 1 and EndTime > ago(queryfrequency)\n | mv-expand UserId to typeof(string)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// Combine and Deduplicate Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by tostring(DestinationMailAddress), ClientIP;\n// Final Output\nCombinedEvents\n | project StartTime, EndTime, DestinationMailAddress, ClientIP, DistinctUserCount, UserId, Ports, EventCount, AccountName, AccountUPNSuffix\n", - "queryFrequency": "P1D", - "queryPeriod": "P7D", - "severity": "Medium", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 0, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" - }, - { - "dataTypes": [ - "OfficeActivity (Exchange)" - ], - "connectorId": "Office365" - } - ], - "tactics": [ - "Collection", - "Exfiltration" - ], - "techniques": [ - "T1114", - "T1020" - ], - "entityMappings": [ - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "UserId", - "identifier": "FullName" - }, - { - "columnName": "AccountName", - "identifier": "Name" - }, - { - "columnName": "AccountUPNSuffix", - "identifier": "UPNSuffix" - } - ] - }, - { - "entityType": "IP", - "fieldMappings": [ - { - "columnName": "ClientIP", - "identifier": "Address" - } - ] - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject9').analyticRuleId9,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 9", - "parentId": "[variables('analyticRuleObject9').analyticRuleId9]", - "contentId": "[variables('analyticRuleObject9')._analyticRulecontentId9]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject9').analyticRuleVersion9]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject9')._analyticRulecontentId9]", - "contentKind": "AnalyticsRule", - "displayName": "GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination", - "contentProductId": "[variables('analyticRuleObject9')._analyticRulecontentProductId9]", - "id": "[variables('analyticRuleObject9')._analyticRulecontentProductId9]", - "version": "[variables('analyticRuleObject9').analyticRuleVersion9]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject10').analyticRuleTemplateSpecName10]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "Office 365 - office_policytampering_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject10').analyticRuleVersion10]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject10')._analyticRulecontentId10]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "Identifies if any tampering is done to either audit log, ATP Safelink, SafeAttachment, AntiPhish, or Dlp policy. \nAn adversary may use this technique to evade detection or avoid other policy-based defenses.\nReferences: https://docs.microsoft.com/powershell/module/exchange/advanced-threat-protection/remove-antiphishrule?view=exchange-ps.", - "displayName": "GSA Enriched Office 365 - Office Policy Tampering", - "enabled": false, - "query": "// Query for EnrichedMicrosoft365AuditLogs\nlet enrichedOpList = EnrichedMicrosoft365AuditLogs \n | summarize by Operation\n | where Operation has_any (\"Remove\", \"Disable\")\n | where Operation contains \"AntiPhish\" \n or Operation contains \"SafeAttachment\" \n or Operation contains \"SafeLinks\" \n or Operation contains \"Dlp\" \n or Operation contains \"Audit\"\n | summarize make_set(Operation, 500);\n\nlet enrichedLogs = EnrichedMicrosoft365AuditLogs\n | where RecordType == \"ExchangeAdmin\"\n | where UserType in~ (\"Admin\", \"DcAdmin\")\n | where Operation in~ (enrichedOpList)\n | extend ClientIPOnly = case( \n ClientIp has \".\", tostring(split(ClientIp, \":\")[0]), \n ClientIp has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIp, \"]\")[0]))),\n ClientIp\n ) \n | extend Port = case(\n ClientIp has \".\", tostring(split(ClientIp, \":\")[1]),\n ClientIp has \"[\", tostring(split(ClientIp, \"]:\")[1]),\n \"\"\n )\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() \n by Operation, UserType, UserId, ClientIP = ClientIPOnly, Port, ResultStatus, Parameters = tostring(AdditionalProperties.Parameters)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Query for OfficeActivity\nlet officeOpList = OfficeActivity \n | summarize by Operation\n | where Operation has_any (\"Remove\", \"Disable\")\n | where Operation contains \"AntiPhish\" \n or Operation contains \"SafeAttachment\" \n or Operation contains \"SafeLinks\" \n or Operation contains \"Dlp\" \n or Operation contains \"Audit\"\n | summarize make_set(Operation, 500);\n\nlet officeLogs = OfficeActivity\n | where RecordType =~ \"ExchangeAdmin\"\n | where UserType in~ (\"Admin\",\"DcAdmin\")\n | where Operation in~ (officeOpList)\n | extend ClientIPOnly = case( \n ClientIP has \".\", tostring(split(ClientIP,\":\")[0]), \n ClientIP has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIP,\"]\")[0]))),\n ClientIP\n ) \n | extend Port = case(\n ClientIP has \".\", tostring(split(ClientIP,\":\")[1]),\n ClientIP has \"[\", tostring(split(ClientIP, \"]:\")[1]),\n \"\"\n )\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() \n by Operation, UserType, UserId, ClientIP = ClientIPOnly, Port, ResultStatus, Parameters\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Combine Enriched Logs and Office Activity Logs\nunion isfuzzy=true enrichedLogs, officeLogs\n| summarize StartTimeUtc = min(StartTimeUtc), EndTimeUtc = max(EndTimeUtc), TotalOperationCount = sum(OperationCount) \n by Operation, UserType, UserId, ClientIP, Port, ResultStatus, Parameters, AccountName, AccountUPNSuffix\n| order by StartTimeUtc desc;\n", - "queryFrequency": "P1D", - "queryPeriod": "P1D", - "severity": "Medium", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 0, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" - }, - { - "dataTypes": [ - "OfficeActivity (Exchange)" - ], - "connectorId": "Office365" - } - ], - "tactics": [ - "Persistence", - "DefenseEvasion" - ], - "techniques": [ - "T1098", - "T1562" - ], - "entityMappings": [ - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "UserId", - "identifier": "FullName" - }, - { - "columnName": "AccountName", - "identifier": "Name" - }, - { - "columnName": "AccountUPNSuffix", - "identifier": "UPNSuffix" - } - ] - }, - { - "entityType": "IP", - "fieldMappings": [ - { - "columnName": "ClientIP", - "identifier": "Address" - } - ] - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject10').analyticRuleId10,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 10", - "parentId": "[variables('analyticRuleObject10').analyticRuleId10]", - "contentId": "[variables('analyticRuleObject10')._analyticRulecontentId10]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject10').analyticRuleVersion10]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject10')._analyticRulecontentId10]", - "contentKind": "AnalyticsRule", - "displayName": "GSA Enriched Office 365 - Office Policy Tampering", - "contentProductId": "[variables('analyticRuleObject10')._analyticRulecontentProductId10]", - "id": "[variables('analyticRuleObject10')._analyticRulecontentProductId10]", - "version": "[variables('analyticRuleObject10').analyticRuleVersion10]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject11').analyticRuleTemplateSpecName11]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "Office 365 - Office_Uploaded_Executables_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject11').analyticRuleVersion11]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject11')._analyticRulecontentId11]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "Identifies when executable file types are uploaded to Office services such as SharePoint and OneDrive.\nList currently includes exe, inf, gzip, cmd, bat file extensions.\nAdditionally, identifies when a given user is uploading these files to another user's workspace.\nThis may be an indication of a staging location for malware or other malicious activity.", - "displayName": "GSA Enriched Office 365 - New Executable via Office FileUploaded Operation", - "enabled": false, - "query": "// Set query parameters\nlet threshold = 2;\nlet uploadOp = 'FileUploaded';\n// Extensions that are interesting. Add/Remove to this list as you see fit\nlet execExt = dynamic(['exe', 'inf', 'gzip', 'cmd', 'bat']);\nlet starttime = 8d;\nlet endtime = 1d;\n\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated >= ago(endtime)\n | where Operation =~ uploadOp\n | where SourceFileExtension has_any (execExt)\n | extend RecordType = coalesce(tostring(RecordType), \"UnknownRecordType\") // Ensure RecordType is a string\n | project TimeGenerated, OfficeId, OfficeWorkload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, UserAgent, Site_Url, SourceRelativeUrl, SourceFileName\n | join kind=leftanti (\n OfficeActivity\n | where TimeGenerated between (ago(starttime) .. ago(endtime))\n | where Operation =~ uploadOp\n | where SourceFileExtension has_any (execExt)\n | summarize SourceRelativeUrl = make_set(SourceRelativeUrl, 100000), UserId = make_set(UserId, 100000), PrevSeenCount = count() by SourceFileName\n // Uncomment the line below to enforce the threshold\n //| where PrevSeenCount > threshold\n | mvexpand SourceRelativeUrl, UserId\n | extend SourceRelativeUrl = tostring(SourceRelativeUrl), UserId = tostring(UserId) // Ensure consistent types for SourceRelativeUrl and UserId\n ) on SourceFileName, SourceRelativeUrl, UserId\n | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])\n | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), UserAgents = make_list(UserAgent, 100000), OfficeIds = make_list(OfficeId, 100000), SourceRelativeUrls = make_list(SourceRelativeUrl, 100000), FileNames = make_list(tostring(SourceFileName), 100000) // Cast FileNames to string\n by OfficeWorkload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated >= ago(endtime)\n | where Operation == uploadOp\n | extend SourceFileExtension = extract(@\"\\.([^\\./]+)$\", 1, tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)) // Extract file extension\n | where SourceFileExtension in (execExt)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl)\n | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | extend RecordType = coalesce(tostring(RecordType), \"UnknownRecordType\") // Ensure RecordType is a string\n | project TimeGenerated, Id, Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent), Site_Url, SourceRelativeUrl, SourceFileName\n | join kind=leftanti (\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (ago(starttime) .. ago(endtime))\n | where Operation == uploadOp\n | extend SourceFileExtension = extract(@\"\\.([^\\./]+)$\", 1, tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)) // Extract file extension\n | where SourceFileExtension in (execExt)\n | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl)\n | summarize SourceRelativeUrl = make_set(SourceRelativeUrl, 100000), UserId = make_set(UserId, 100000), PrevSeenCount = count() by SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n // Uncomment the line below to enforce the threshold\n //| where PrevSeenCount > threshold\n | mvexpand SourceRelativeUrl, UserId\n | extend SourceRelativeUrl = tostring(SourceRelativeUrl), UserId = tostring(UserId) // Ensure consistent types for SourceRelativeUrl and UserId\n ) on SourceFileName, SourceRelativeUrl, UserId\n | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])\n | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), UserAgents = make_list(UserAgent, 100000), Ids = make_list(Id, 100000), SourceRelativeUrls = make_list(tostring(SourceRelativeUrl), 100000), FileNames = make_list(tostring(SourceFileName), 100000) // Cast FileNames to string\n by Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Combine Office and Enriched Logs\nlet CombinedEvents = EnrichedEvents\n | union isfuzzy=true OfficeEvents\n | summarize arg_min(StartTime, *) by tostring(FileNames), UserId, Site_Url, tostring(RecordType) // Ensure FileNames and RecordType are strings\n | order by StartTime desc;\n\n// Final Output\nCombinedEvents\n | project StartTime, EndTime, Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder, FileNames, UserAgents, AccountName, AccountUPNSuffix;\n", - "queryFrequency": "P1D", - "queryPeriod": "P8D", - "severity": "Low", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 0, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" - }, - { - "dataTypes": [ - "OfficeActivity (SharePoint)" - ], - "connectorId": "Office365" - } - ], - "tactics": [ - "CommandAndControl", - "LateralMovement" - ], - "techniques": [ - "T1105", - "T1570" - ], - "entityMappings": [ - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "UserId", - "identifier": "FullName" - }, - { - "columnName": "AccountName", - "identifier": "Name" - }, - { - "columnName": "AccountUPNSuffix", - "identifier": "UPNSuffix" - } - ] - }, - { - "entityType": "IP", - "fieldMappings": [ - { - "columnName": "ClientIP", - "identifier": "Address" - } - ] - }, - { - "entityType": "URL", - "fieldMappings": [ - { - "columnName": "Site_Url", - "identifier": "Url" - } - ] - }, - { - "entityType": "File", - "fieldMappings": [ - { - "columnName": "FileNames", - "identifier": "Name" - } - ] - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject11').analyticRuleId11,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 11", - "parentId": "[variables('analyticRuleObject11').analyticRuleId11]", - "contentId": "[variables('analyticRuleObject11')._analyticRulecontentId11]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject11').analyticRuleVersion11]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject11')._analyticRulecontentId11]", - "contentKind": "AnalyticsRule", - "displayName": "GSA Enriched Office 365 - New Executable via Office FileUploaded Operation", - "contentProductId": "[variables('analyticRuleObject11')._analyticRulecontentProductId11]", - "id": "[variables('analyticRuleObject11')._analyticRulecontentProductId11]", - "version": "[variables('analyticRuleObject11').analyticRuleVersion11]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject12').analyticRuleTemplateSpecName12]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "Office 365 - RareOfficeOperations_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject12').analyticRuleVersion12]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject12')._analyticRulecontentId12]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "Identifies Office operations that are typically rare and can provide capabilities useful to attackers.", - "displayName": "GSA Enriched Office 365 - Rare and Potentially High-Risk Office Operations", - "enabled": false, - "query": "// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where Operation in~ ( \"Add-MailboxPermission\", \"Add-MailboxFolderPermission\", \"Set-Mailbox\", \"New-ManagementRoleAssignment\", \"New-InboxRule\", \"Set-InboxRule\", \"Set-TransportRule\")\n and not(UserId has_any ('NT AUTHORITY\\\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\\\SYSTEM (Microsoft.Exchange.AdminApi.NetCore)', 'NT AUTHORITY\\\\SYSTEM (w3wp)', 'devilfish-applicationaccount') \n and Operation in~ ( \"Add-MailboxPermission\", \"Set-Mailbox\"))\n | extend ClientIPOnly = tostring(extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?', dynamic([\"IPAddress\"]), ClientIP)[0])\n | extend AccountName = tostring(split(UserId, \"@\")[0]), \n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Operation in~ ( \"Add-MailboxPermission\", \"Add-MailboxFolderPermission\", \"Set-Mailbox\", \"New-ManagementRoleAssignment\", \"New-InboxRule\", \"Set-InboxRule\", \"Set-TransportRule\")\n and not(UserId has_any ('NT AUTHORITY\\\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\\\SYSTEM (Microsoft.Exchange.AdminApi.NetCore)', 'NT AUTHORITY\\\\SYSTEM (w3wp)', 'devilfish-applicationaccount') \n and Operation in~ ( \"Add-MailboxPermission\", \"Set-Mailbox\"))\n | extend ClientIPOnly = tostring(extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?', dynamic([\"IPAddress\"]), ClientIp)[0])\n | extend AccountName = tostring(split(UserId, \"@\")[0]), \n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Combine and Deduplicate Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeGenerated, *) by Operation, UserId, ClientIPOnly;\n\n// Final Output\nCombinedEvents\n | project TimeGenerated, Operation, UserId, AccountName, AccountUPNSuffix, ClientIPOnly\n", - "queryFrequency": "P1D", - "queryPeriod": "P1D", - "severity": "Low", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 0, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" - }, - { - "dataTypes": [ - "OfficeActivity" - ], - "connectorId": "Office365" - } - ], - "tactics": [ - "Persistence", - "Collection" - ], - "techniques": [ - "T1098", - "T1114" - ], - "entityMappings": [ - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "UserId", - "identifier": "FullName" - }, - { - "columnName": "AccountName", - "identifier": "Name" - }, - { - "columnName": "AccountUPNSuffix", - "identifier": "UPNSuffix" - } - ] - }, - { - "entityType": "IP", - "fieldMappings": [ - { - "columnName": "ClientIPOnly", - "identifier": "Address" - } - ] - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject12').analyticRuleId12,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 12", - "parentId": "[variables('analyticRuleObject12').analyticRuleId12]", - "contentId": "[variables('analyticRuleObject12')._analyticRulecontentId12]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject12').analyticRuleVersion12]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject12')._analyticRulecontentId12]", - "contentKind": "AnalyticsRule", - "displayName": "GSA Enriched Office 365 - Rare and Potentially High-Risk Office Operations", - "contentProductId": "[variables('analyticRuleObject12')._analyticRulecontentProductId12]", - "id": "[variables('analyticRuleObject12')._analyticRulecontentProductId12]", - "version": "[variables('analyticRuleObject12').analyticRuleVersion12]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject13').analyticRuleTemplateSpecName13]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "Office 365 - SharePoint_Downloads_byNewIP_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject13').analyticRuleVersion13]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject13')._analyticRulecontentId13]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "Identifies anomalies using user behavior by setting a threshold for significant changes in file upload/download activities from new IP addresses. It establishes a baseline of typical behavior, compares it to recent activity, and flags deviations exceeding a default threshold of 25.", - "displayName": "GSA Enriched Office 365 - SharePoint File Operation via Previously Unseen IPs", - "enabled": false, - "query": "let threshold = 25;\nlet szSharePointFileOperation = \"SharePointFileOperation\";\nlet szOperations = dynamic([\"FileDownloaded\", \"FileUploaded\"]);\nlet starttime = 14d;\nlet endtime = 1d;\n\n// Define a baseline of normal user behavior for OfficeActivity\nlet userBaselineOffice = OfficeActivity\n | where TimeGenerated between(ago(starttime)..ago(endtime))\n | where RecordType =~ szSharePointFileOperation\n | where Operation in~ (szOperations)\n | where isnotempty(UserAgent)\n | summarize Count = count() by UserId, Operation, Site_Url, ClientIP\n | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url, ClientIP;\n\n// Get recent user activity for OfficeActivity\nlet recentUserActivityOffice = OfficeActivity\n | where TimeGenerated > ago(endtime)\n | where RecordType =~ szSharePointFileOperation\n | where Operation in~ (szOperations)\n | where isnotempty(UserAgent)\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), RecentCount = count() by UserId, UserType, Operation, Site_Url, ClientIP, OfficeObjectId, OfficeWorkload, UserAgent;\n\n// Define a baseline of normal user behavior for EnrichedMicrosoft365AuditLogs\nlet userBaselineEnriched = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between(ago(starttime)..ago(endtime))\n | where RecordType == szSharePointFileOperation\n | where Operation in (szOperations)\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(UserAgent)\n | summarize Count = count() by UserId, Operation, Site_Url, ClientIp\n | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url, ClientIp;\n\n// Get recent user activity for EnrichedMicrosoft365AuditLogs\nlet recentUserActivityEnriched = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated > ago(endtime)\n | where RecordType == szSharePointFileOperation\n | where Operation in (szOperations)\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(UserAgent)\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), RecentCount = count() by UserId, UserType, Operation, Site_Url, ClientIp, ObjectId, Workload, UserAgent;\n\n// Combine user baselines and recent activity, calculate deviation, and deduplicate\nlet UserBehaviorAnalysis = userBaselineOffice\n | join kind=inner (recentUserActivityOffice) on UserId, Operation, Site_Url, ClientIP\n | union (userBaselineEnriched | join kind=inner (recentUserActivityEnriched) on UserId, Operation, Site_Url, ClientIp)\n | extend Deviation = abs(RecentCount - AvgCount) / AvgCount;\n\n// Filter for significant deviations\nUserBehaviorAnalysis\n | where Deviation > threshold\n | project StartTimeUtc, EndTimeUtc, UserId, UserType, Operation, ClientIP, Site_Url, ObjectId, OfficeObjectId, OfficeWorkload, Workload, UserAgent, Deviation, Count = RecentCount\n | order by Count desc, ClientIP asc, Operation asc, UserId asc\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n", - "queryFrequency": "P1D", - "queryPeriod": "P14D", - "severity": "Medium", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 0, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" - }, - { - "dataTypes": [ - "OfficeActivity (SharePoint)" - ], - "connectorId": "Office365" - } - ], - "tactics": [ - "Exfiltration" - ], - "techniques": [ - "T1030" - ], - "entityMappings": [ - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "UserId", - "identifier": "FullName" - }, - { - "columnName": "AccountName", - "identifier": "Name" - }, - { - "columnName": "AccountUPNSuffix", - "identifier": "UPNSuffix" - } - ] - }, - { - "entityType": "IP", - "fieldMappings": [ - { - "columnName": "ClientIP", - "identifier": "Address" - } - ] - }, - { - "entityType": "URL", - "fieldMappings": [ - { - "columnName": "Site_Url", - "identifier": "Url" - } - ] - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject13').analyticRuleId13,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 13", - "parentId": "[variables('analyticRuleObject13').analyticRuleId13]", - "contentId": "[variables('analyticRuleObject13')._analyticRulecontentId13]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject13').analyticRuleVersion13]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject13')._analyticRulecontentId13]", - "contentKind": "AnalyticsRule", - "displayName": "GSA Enriched Office 365 - SharePoint File Operation via Previously Unseen IPs", - "contentProductId": "[variables('analyticRuleObject13')._analyticRulecontentProductId13]", - "id": "[variables('analyticRuleObject13')._analyticRulecontentProductId13]", - "version": "[variables('analyticRuleObject13').analyticRuleVersion13]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject14').analyticRuleTemplateSpecName14]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "Office 365 - SharePoint_Downloads_byNewUserAgent_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject14').analyticRuleVersion14]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject14')._analyticRulecontentId14]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "Identifies anomalies if the number of documents uploaded or downloaded from device(s) associated with a previously unseen user agent exceeds a threshold (default is 5) and deviation (default is 25%).", - "displayName": "GSA Enriched Office 365 - SharePointFileOperation via devices with previously unseen user agents", - "enabled": false, - "query": "let threshold = 5;\nlet szSharePointFileOperation = \"SharePointFileOperation\";\nlet szOperations = dynamic([\"FileDownloaded\", \"FileUploaded\"]);\nlet starttime = 14d;\nlet endtime = 1d;\n\n// OfficeActivity - Base Events\nlet BaseeventsOffice = OfficeActivity\n | where TimeGenerated between (ago(starttime)..ago(endtime))\n | where RecordType =~ szSharePointFileOperation\n | where Operation in~ (szOperations)\n | where isnotempty(UserAgent);\n\n// OfficeActivity - Frequent User Agents\nlet FrequentUAOffice = BaseeventsOffice\n | summarize FUACount = count() by UserAgent, RecordType, Operation\n | where FUACount >= threshold\n | distinct UserAgent;\n\n// OfficeActivity - User Baseline\nlet UserBaseLineOffice = BaseeventsOffice\n | summarize Count = count() by UserId, Operation, Site_Url\n | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url;\n\n// OfficeActivity - Recent User Activity\nlet RecentActivityOffice = OfficeActivity\n | where TimeGenerated > ago(endtime)\n | where RecordType =~ szSharePointFileOperation\n | where Operation in~ (szOperations)\n | where isnotempty(UserAgent)\n | where UserAgent in~ (FrequentUAOffice)\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OfficeObjectIdCount = dcount(OfficeObjectId), OfficeObjectIdList = make_set(OfficeObjectId), UserAgentSeenCount = count()\n by RecordType, Operation, UserAgent, UserType, UserId, ClientIP , OfficeWorkload, Site_Url;\n\n// EnrichedMicrosoft365AuditLogs - Base Events\nlet BaseeventsEnriched = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (ago(starttime)..ago(endtime))\n | where RecordType == szSharePointFileOperation\n | where Operation in (szOperations)\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(UserAgent);\n\n// EnrichedMicrosoft365AuditLogs - Frequent User Agents\nlet FrequentUAEnriched = BaseeventsEnriched\n | summarize FUACount = count() by UserAgent, RecordType, Operation\n | where FUACount >= threshold\n | distinct UserAgent;\n\n// EnrichedMicrosoft365AuditLogs - User Baseline\nlet UserBaseLineEnriched = BaseeventsEnriched\n | summarize Count = count() by UserId, Operation, Site_Url\n | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url;\n\n// EnrichedMicrosoft365AuditLogs - Recent User Activity\nlet RecentActivityEnriched = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated > ago(endtime)\n | where RecordType == szSharePointFileOperation\n | where Operation in (szOperations)\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | extend ClientIP = ClientIp\n | where isnotempty(UserAgent)\n | where UserAgent in (FrequentUAEnriched)\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ObjectIdCount = dcount(ObjectId), ObjectIdList = make_set(ObjectId), UserAgentSeenCount = count()\n by RecordType, Operation, UserAgent, UserId,ClientIP, Site_Url;\n\n// Combine Baseline and Recent Activity, Calculate Deviation, and Deduplicate\nlet UserBehaviorAnalysisOffice = UserBaseLineOffice\n | join kind=inner (RecentActivityOffice) on UserId, Operation, Site_Url\n | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount;\n\nlet UserBehaviorAnalysisEnriched = UserBaseLineEnriched\n | join kind=inner (RecentActivityEnriched) on UserId, Operation, Site_Url\n | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount;\n\n// Combine Office and Enriched Logs\nlet CombinedUserBehaviorAnalysis = UserBehaviorAnalysisOffice\n | union UserBehaviorAnalysisEnriched;\n\n// Filter and Format Final Results\nCombinedUserBehaviorAnalysis\n | where Deviation > 0.25\n | extend UserIdName = tostring(split(UserId, '@')[0]), \n UserIdUPNSuffix = tostring(split(UserId, '@')[1])\n | project-reorder StartTimeUtc, EndTimeUtc, UserAgent, UserAgentSeenCount, UserId, ClientIP, Site_Url\n | order by UserAgentSeenCount desc, UserAgent asc, UserId asc, Site_Url asc\n", - "queryFrequency": "P1D", - "queryPeriod": "P14D", - "severity": "Medium", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 0, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" - }, - { - "dataTypes": [ - "OfficeActivity (SharePoint)" - ], - "connectorId": "Office365" - } - ], - "tactics": [ - "Exfiltration" - ], - "techniques": [ - "T1030" - ], - "entityMappings": [ - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "UserId", - "identifier": "FullName" - }, - { - "columnName": "UserIdName", - "identifier": "Name" - }, - { - "columnName": "UserIdUPNSuffix", - "identifier": "UPNSuffix" - } - ] - }, - { - "entityType": "IP", - "fieldMappings": [ - { - "columnName": "ClientIP", - "identifier": "Address" - } - ] - }, - { - "entityType": "URL", - "fieldMappings": [ - { - "columnName": "Site_Url", - "identifier": "Url" - } - ] - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject14').analyticRuleId14,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 14", - "parentId": "[variables('analyticRuleObject14').analyticRuleId14]", - "contentId": "[variables('analyticRuleObject14')._analyticRulecontentId14]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject14').analyticRuleVersion14]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject14')._analyticRulecontentId14]", - "contentKind": "AnalyticsRule", - "displayName": "GSA Enriched Office 365 - SharePointFileOperation via devices with previously unseen user agents", - "contentProductId": "[variables('analyticRuleObject14')._analyticRulecontentProductId14]", - "id": "[variables('analyticRuleObject14')._analyticRulecontentProductId14]", - "version": "[variables('analyticRuleObject14').analyticRuleVersion14]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject15').analyticRuleTemplateSpecName15]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "Office 365 - sharepoint_file_transfer_above_threshold_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject15').analyticRuleVersion15]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject15')._analyticRulecontentId15]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "Identifies Office365 Sharepoint File Transfers above a certain threshold in a 15-minute time period.\nPlease note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur.", - "displayName": "GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold", - "enabled": false, - "query": "let threshold = 5000;\n\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload has_any(\"SharePoint\", \"OneDrive\") \n | where Operation has_any(\"FileDownloaded\", \"FileSyncDownloadedFull\", \"FileSyncUploadedFull\", \"FileUploaded\")\n | extend ClientIP = ClientIp\n | summarize count_distinct_ObjectId = dcount(ObjectId), fileslist = make_set(ObjectId, 10000) \n by UserId, ClientIP, bin(TimeGenerated, 15m)\n | where count_distinct_ObjectId >= threshold\n | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat(\"SeeFilesListField\", \"_\", tostring(hash(tostring(fileslist)))))\n | extend AccountName = tostring(split(UserId, \"@\")[0]), \n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where EventSource == \"SharePoint\" \n | where OfficeWorkload has_any(\"SharePoint\", \"OneDrive\") \n | where Operation has_any(\"FileDownloaded\", \"FileSyncDownloadedFull\", \"FileSyncUploadedFull\", \"FileUploaded\")\n | summarize count_distinct_OfficeObjectId = dcount(OfficeObjectId), fileslist = make_set(OfficeObjectId, 10000) \n by UserId, ClientIP, bin(TimeGenerated, 15m)\n | where count_distinct_OfficeObjectId >= threshold\n | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat(\"SeeFilesListField\", \"_\", tostring(hash(tostring(fileslist)))))\n | extend AccountName = tostring(split(UserId, \"@\")[0]), \n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeGenerated, *) by UserId, ClientIP;\n\n// Final Output\nCombinedEvents\n | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, count_distinct_ObjectId, fileslist\n | order by TimeGenerated desc\n", - "queryFrequency": "PT15M", - "queryPeriod": "PT15M", - "severity": "Medium", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 0, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" - }, - { - "dataTypes": [ - "OfficeActivity (SharePoint)" - ], - "connectorId": "Office365" - } - ], - "tactics": [ - "Exfiltration" - ], - "techniques": [ - "T1020" - ], - "entityMappings": [ - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "UserId", - "identifier": "FullName" - }, - { - "columnName": "AccountName", - "identifier": "Name" - }, - { - "columnName": "AccountUPNSuffix", - "identifier": "UPNSuffix" - } - ] - }, - { - "entityType": "IP", - "fieldMappings": [ - { - "columnName": "ClientIP", - "identifier": "Address" - } - ] - }, - { - "entityType": "File", - "fieldMappings": [ - { - "columnName": "FileSample", - "identifier": "Name" - } - ] - } - ], - "customDetails": { - "TransferCount": "count_distinct_OfficeObjectId", - "FilesList": "fileslist" - }, - "incidentConfiguration": { - "groupingConfiguration": { - "enabled": true, - "lookbackDuration": "PT5H", - "groupByEntities": [ - "Account" - ], - "matchingMethod": "Selected", - "reopenClosedIncident": false, - "groupByCustomDetails": [], - "groupByAlertDetails": [] - }, - "createIncident": true - } - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject15').analyticRuleId15,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 15", - "parentId": "[variables('analyticRuleObject15').analyticRuleId15]", - "contentId": "[variables('analyticRuleObject15')._analyticRulecontentId15]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject15').analyticRuleVersion15]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject15')._analyticRulecontentId15]", - "contentKind": "AnalyticsRule", - "displayName": "GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold", - "contentProductId": "[variables('analyticRuleObject15')._analyticRulecontentProductId15]", - "id": "[variables('analyticRuleObject15')._analyticRulecontentProductId15]", - "version": "[variables('analyticRuleObject15').analyticRuleVersion15]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject16').analyticRuleTemplateSpecName16]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "Office 365 - sharepoint_file_transfer_folders_above_threshold_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject16').analyticRuleVersion16]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject16')._analyticRulecontentId16]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "Identifies Office365 Sharepoint File Transfers with a distinct folder count above a certain threshold in a 15-minute time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur.", - "displayName": "GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold", - "enabled": false, - "query": "let threshold = 5000;\n // EnrichedMicrosoft365AuditLogs Query\n let EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload has_any(\"SharePoint\", \"OneDrive\")\n | where Operation has_any(\"FileDownloaded\", \"FileSyncDownloadedFull\", \"FileSyncUploadedFull\", \"FileUploaded\")\n | extend UserAgent = tostring(AdditionalProperties[\"UserAgent\"])\n | summarize count_distinct_ObjectId = dcount(ObjectId), \n fileslist = make_set(ObjectId, 10000), \n any_UserAgent = any(UserAgent)\n by UserId, ClientIP = ClientIp, bin(TimeGenerated, 15m)\n | where count_distinct_ObjectId >= threshold\n | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat(\"SeeFilesListField\", \"_\", tostring(hash(tostring(fileslist)))))\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n // OfficeActivity Query\n let OfficeEvents = OfficeActivity\n | where EventSource == \"SharePoint\"\n | where OfficeWorkload has_any(\"SharePoint\", \"OneDrive\")\n | where Operation has_any(\"FileDownloaded\", \"FileSyncDownloadedFull\", \"FileSyncUploadedFull\", \"FileUploaded\")\n | summarize count_distinct_OfficeObjectId = dcount(OfficeObjectId), \n fileslist = make_set(OfficeObjectId, 10000), \n any_UserAgent = any(UserAgent)\n by UserId, ClientIP, bin(TimeGenerated, 15m)\n | where count_distinct_OfficeObjectId >= threshold\n | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat(\"SeeFilesListField\", \"_\", tostring(hash(tostring(fileslist)))))\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n // Combine Office and Enriched Logs\n let CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeGenerated, *) by UserId, ClientIP, any_UserAgent;\n // Final Output\n CombinedEvents\n | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, UserAgent = any_UserAgent, count_distinct_ObjectId = coalesce(count_distinct_ObjectId, count_distinct_OfficeObjectId), fileslist\n | order by TimeGenerated desc\n", - "queryFrequency": "PT15M", - "queryPeriod": "PT15M", - "severity": "Medium", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 0, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" - }, - { - "dataTypes": [ - "OfficeActivity (SharePoint)" - ], - "connectorId": "Office365" - } - ], - "tactics": [ - "Exfiltration" - ], - "techniques": [ - "T1020" - ], - "entityMappings": [ - { - "entityType": "Account", - "fieldMappings": [ - { - "columnName": "UserId", - "identifier": "FullName" - }, - { - "columnName": "AccountName", - "identifier": "Name" - }, - { - "columnName": "AccountUPNSuffix", - "identifier": "UPNSuffix" - } - ] - }, - { - "entityType": "IP", - "fieldMappings": [ - { - "columnName": "ClientIP", - "identifier": "Address" - } - ] - }, - { - "entityType": "File", - "fieldMappings": [ - { - "columnName": "FileSample", - "identifier": "Name" - } - ] - } - ], - "customDetails": { - "TransferCount": "count_distinct_OfficeObjectId", - "FilesList": "fileslist" - }, - "incidentConfiguration": { - "groupingConfiguration": { - "enabled": true, - "lookbackDuration": "PT5H", - "groupByEntities": [ - "Account" - ], - "matchingMethod": "Selected", - "reopenClosedIncident": false, - "groupByCustomDetails": [], - "groupByAlertDetails": [] - }, - "createIncident": true - } - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject16').analyticRuleId16,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 16", - "parentId": "[variables('analyticRuleObject16').analyticRuleId16]", - "contentId": "[variables('analyticRuleObject16')._analyticRulecontentId16]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject16').analyticRuleVersion16]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject16')._analyticRulecontentId16]", - "contentKind": "AnalyticsRule", - "displayName": "GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold", - "contentProductId": "[variables('analyticRuleObject16')._analyticRulecontentProductId16]", - "id": "[variables('analyticRuleObject16')._analyticRulecontentProductId16]", - "version": "[variables('analyticRuleObject16').analyticRuleVersion16]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject17').analyticRuleTemplateSpecName17]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "SWG - Abnormal Deny Rate_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject17').analyticRuleVersion17]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject17')._analyticRulecontentId17]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "Identifies abnormal deny rate for specific source IP to destination IP based on the normal average and standard deviation learned during a configured period. This can indicate potential exfiltration, initial access, or C2, where an attacker tries to exploit the same vulnerability on machines in the organization but is being blocked by firewall rules.", - "displayName": "Detect Abnormal Deny Rate for Source to Destination IP", - "enabled": false, - "query": "let NumOfStdsThreshold = 3;\nlet LearningPeriod = 5d;\nlet BinTime = 1h;\nlet MinThreshold = 5.0;\nlet MinLearningBuckets = 5;\nlet TrafficLogs = NetworkAccessTraffic\n | where Action == 'Denied'\n | where isnotempty(DestinationIp) and isnotempty(SourceIp);\nlet LearningSrcIpDenyRate = TrafficLogs\n | where TimeGenerated between (ago(LearningPeriod + 1d) .. ago(1d))\n | summarize count() by SourceIp, bin(TimeGenerated, BinTime), DestinationIp\n | summarize LearningTimeSrcIpDenyRateAvg = avg(count_), LearningTimeSrcIpDenyRateStd = stdev(count_), LearningTimeBuckets = count() by SourceIp, DestinationIp\n | where LearningTimeBuckets > MinLearningBuckets;\nlet AlertTimeSrcIpDenyRate = TrafficLogs\n | where TimeGenerated between (ago(1h) .. now())\n | summarize AlertTimeSrcIpDenyRateCount = count() by SourceIp, DestinationIp;\nAlertTimeSrcIpDenyRate\n | join kind=leftouter (LearningSrcIpDenyRate) on SourceIp, DestinationIp\n | extend LearningThreshold = max_of(LearningTimeSrcIpDenyRateAvg + NumOfStdsThreshold * LearningTimeSrcIpDenyRateStd, MinThreshold)\n | where AlertTimeSrcIpDenyRateCount > LearningThreshold\n | project SourceIp, DestinationIp, AlertTimeSrcIpDenyRateCount, LearningThreshold \n", - "queryFrequency": "PT1H", - "queryPeriod": "PT25H", - "severity": "Medium", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 1, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "NetworkAccessTrafficLogs" - ], - "connectorId": "AzureActiveDirectory" - } - ], - "tactics": [ - "InitialAccess", - "Exfiltration", - "CommandAndControl" - ], - "entityMappings": [ - { - "entityType": "IP", - "fieldMappings": [ - { - "columnName": "SourceIp", - "identifier": "Address" - } - ] - }, - { - "entityType": "URL", - "fieldMappings": [ - { - "columnName": "DestinationIp", - "identifier": "Url" - } - ] - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject17').analyticRuleId17,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 17", - "parentId": "[variables('analyticRuleObject17').analyticRuleId17]", - "contentId": "[variables('analyticRuleObject17')._analyticRulecontentId17]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject17').analyticRuleVersion17]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject17')._analyticRulecontentId17]", - "contentKind": "AnalyticsRule", - "displayName": "Detect Abnormal Deny Rate for Source to Destination IP", - "contentProductId": "[variables('analyticRuleObject17')._analyticRulecontentProductId17]", - "id": "[variables('analyticRuleObject17')._analyticRulecontentProductId17]", - "version": "[variables('analyticRuleObject17').analyticRuleVersion17]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject18').analyticRuleTemplateSpecName18]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "SWG - Abnormal Port to Protocol_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject18').analyticRuleVersion18]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject18')._analyticRulecontentId18]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "Identifies changes in the protocol used for specific destination ports, comparing the current runtime with a learned baseline. This can indicate potential protocol misuse or configuration changes.", - "displayName": "Detect Protocol Changes for Destination Ports", - "enabled": false, - "query": "let LearningPeriod = 7d;\nlet RunTime = 1d;\nlet StartLearningPeriod = ago(LearningPeriod + RunTime);\nlet EndRunTime = ago(RunTime);\nlet LearningPortToProtocol = \n NetworkAccessTraffic\n | where TimeGenerated between (StartLearningPeriod .. EndRunTime)\n | where isnotempty(DestinationPort)\n | summarize LearningTimeCount = count() by LearningTimeDstPort = DestinationPort, LearningTimeProtocol = TransportProtocol, SourceIp, DestinationFqdn;\nlet AlertTimePortToProtocol = \n NetworkAccessTraffic\n | where TimeGenerated between (EndRunTime .. now())\n | where isnotempty(DestinationPort)\n | summarize AlertTimeCount = count() by AlertTimeDstPort = DestinationPort, AlertTimeProtocol = TransportProtocol, SourceIp, DestinationFqdn;\nAlertTimePortToProtocol\n | join kind=leftouter (LearningPortToProtocol) on $left.AlertTimeDstPort == $right.LearningTimeDstPort and $left.SourceIp == $right.SourceIp and $left.DestinationFqdn == $right.DestinationFqdn\n | where isnull(LearningTimeProtocol) or LearningTimeProtocol != AlertTimeProtocol\n | project AlertTimeDstPort, AlertTimeProtocol, LearningTimeProtocol, SourceIp, DestinationFqdn\n | extend IPCustomEntity = SourceIp, FqdnCustomEntity = DestinationFqdn\n", - "queryFrequency": "PT1H", - "queryPeriod": "P8D", - "severity": "Medium", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 1, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" - } - ], - "tactics": [ - "DefenseEvasion", - "Exfiltration", - "CommandAndControl" - ], - "entityMappings": [ - { - "entityType": "IP", - "fieldMappings": [ - { - "columnName": "IPCustomEntity", - "identifier": "Address" - } - ] - }, - { - "entityType": "URL", - "fieldMappings": [ - { - "columnName": "FqdnCustomEntity", - "identifier": "Url" - } - ] - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject18').analyticRuleId18,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 18", - "parentId": "[variables('analyticRuleObject18').analyticRuleId18]", - "contentId": "[variables('analyticRuleObject18')._analyticRulecontentId18]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject18').analyticRuleVersion18]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject18')._analyticRulecontentId18]", - "contentKind": "AnalyticsRule", - "displayName": "Detect Protocol Changes for Destination Ports", - "contentProductId": "[variables('analyticRuleObject18')._analyticRulecontentProductId18]", - "id": "[variables('analyticRuleObject18')._analyticRulecontentProductId18]", - "version": "[variables('analyticRuleObject18').analyticRuleVersion18]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('analyticRuleObject19').analyticRuleTemplateSpecName19]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "SWG - Source IP Port Scan_AnalyticalRules Analytics Rule with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('analyticRuleObject19').analyticRuleVersion19]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.SecurityInsights/AlertRuleTemplates", - "name": "[variables('analyticRuleObject19')._analyticRulecontentId19]", - "apiVersion": "2023-02-01-preview", - "kind": "Scheduled", - "location": "[parameters('workspace-location')]", - "properties": { - "description": "Identifies a source IP scanning multiple open ports on Global Secure Access Firewall. This can indicate malicious scanning of ports by an attacker, trying to reveal open ports in the organization that can be compromised for initial access.", - "displayName": "Detect Source IP Scanning Multiple Open Ports", - "enabled": false, - "query": "let port_scan_time = 30s;\nlet min_ports_threshold = 100;\nNetworkAccessTraffic\n| where TimeGenerated > ago(1d)\n| where Action == 'Allowed'\n| summarize PortsScanned = dcount(DestinationPort) by SourceIp, bin(TimeGenerated, port_scan_time)\n| where PortsScanned > min_ports_threshold\n| project SourceIp, PortsScanned, TimeGenerated\n", - "queryFrequency": "P1D", - "queryPeriod": "P1D", - "severity": "Medium", - "suppressionDuration": "PT1H", - "suppressionEnabled": false, - "triggerOperator": "GreaterThan", - "triggerThreshold": 1, - "status": "Available", - "requiredDataConnectors": [ - { - "dataTypes": [ - "EnrichedMicrosoft365AuditLogs" - ], - "connectorId": "AzureActiveDirectory" - } - ], - "tactics": [ - "Discovery" - ], - "techniques": [ - "T1046" - ], - "entityMappings": [ - { - "entityType": "IP", - "fieldMappings": [ - { - "columnName": "SourceIp", - "identifier": "Address" - } - ] - }, - { - "entityType": "URL", - "fieldMappings": [ - { - "columnName": "Fqdn", - "identifier": "Url" - } - ] - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject19').analyticRuleId19,'/'))))]", - "properties": { - "description": "Global Secure Access Analytics Rule 19", - "parentId": "[variables('analyticRuleObject19').analyticRuleId19]", - "contentId": "[variables('analyticRuleObject19')._analyticRulecontentId19]", - "kind": "AnalyticsRule", - "version": "[variables('analyticRuleObject19').analyticRuleVersion19]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('analyticRuleObject19')._analyticRulecontentId19]", - "contentKind": "AnalyticsRule", - "displayName": "Detect Source IP Scanning Multiple Open Ports", - "contentProductId": "[variables('analyticRuleObject19')._analyticRulecontentProductId19]", - "id": "[variables('analyticRuleObject19')._analyticRulecontentProductId19]", - "version": "[variables('analyticRuleObject19').analyticRuleVersion19]" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject1').huntingQueryTemplateSpecName1]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "AnomolousUserAccessingOtherUsersMailbox_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject1').huntingQueryVersion1]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_1", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "GSA Enriched Office 365 - Anomalous access to other users' mailboxes", - "category": "Hunting Queries", - "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = totimespan((endtime - starttime) * 2);\nlet user_threshold = 1; // Threshold for number of mailboxes accessed\nlet folder_threshold = 5; // Threshold for number of mailbox folders accessed\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated between (ago(lookback)..starttime)\n | where Operation =~ \"MailItemsAccessed\"\n | where ResultStatus =~ \"Succeeded\"\n | where tolower(MailboxOwnerUPN) != tolower(UserId)\n | join kind=rightanti (\n OfficeActivity\n | where TimeGenerated between (starttime..endtime)\n | where Operation =~ \"MailItemsAccessed\"\n | where ResultStatus =~ \"Succeeded\"\n | where tolower(MailboxOwnerUPN) != tolower(UserId)\n ) on MailboxOwnerUPN, UserId\n | where isnotempty(Folders)\n | mv-expand parse_json(Folders)\n | extend folders = tostring(Folders.Path)\n | extend ClientIP = iif(Client_IPAddress startswith \"[\", extract(\"\\\\[([^\\\\]]*)\", 1, Client_IPAddress), Client_IPAddress)\n | summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), set_folders = make_set(folders, 100000), set_ClientInfoString = make_set(ClientInfoString, 100000), set_ClientIP = make_set(ClientIP, 100000), set_MailboxGuid = make_set(MailboxGuid, 100000), set_MailboxOwnerUPN = make_set(MailboxOwnerUPN, 100000) by UserId\n | extend folder_count = array_length(set_folders)\n | extend user_count = array_length(set_MailboxGuid)\n | where user_count > user_threshold or folder_count > folder_threshold\n | extend Reason = case(user_count > user_threshold and folder_count > folder_threshold, \"Both User and Folder Threshold Exceeded\", folder_count > folder_threshold and user_count < user_threshold, \"Folder Count Threshold Exceeded\", \"User Threshold Exceeded\")\n | sort by user_count desc\n | project-reorder UserId, user_count, folder_count, set_MailboxOwnerUPN, set_ClientIP, set_ClientInfoString, set_folders\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (ago(lookback)..starttime)\n | where Operation =~ \"MailItemsAccessed\"\n | where ResultStatus =~ \"Succeeded\"\n | extend MailboxOwnerUPN = tostring(parse_json(AdditionalProperties).MailboxOwnerUPN)\n | where tolower(MailboxOwnerUPN) != tolower(UserId)\n | join kind=rightanti (\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime..endtime)\n | where Operation =~ \"MailItemsAccessed\"\n | where ResultStatus =~ \"Succeeded\"\n | extend MailboxOwnerUPN = tostring(parse_json(AdditionalProperties).MailboxOwnerUPN)\n | where tolower(MailboxOwnerUPN) != tolower(UserId)\n ) on MailboxOwnerUPN, UserId\n | where isnotempty(tostring(parse_json(AdditionalProperties).Folders))\n | mv-expand Folders = parse_json(AdditionalProperties).Folders\n | extend folders = tostring(Folders.Path)\n | extend ClientIP = iif(ClientIp startswith \"[\", extract(\"\\\\[([^\\\\]]*)\", 1, ClientIp), ClientIp)\n | extend ClientInfoString = tostring(parse_json(AdditionalProperties).ClientInfoString)\n | extend MailboxGuid = tostring(parse_json(AdditionalProperties).MailboxGuid)\n | summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), set_folders = make_set(folders, 100000), set_ClientInfoString = make_set(ClientInfoString, 100000), set_ClientIP = make_set(ClientIP, 100000), set_MailboxGuid = make_set(MailboxGuid, 100000), set_MailboxOwnerUPN = make_set(MailboxOwnerUPN, 100000) by UserId\n | extend folder_count = array_length(set_folders)\n | extend user_count = array_length(set_MailboxGuid)\n | where user_count > user_threshold or folder_count > folder_threshold\n | extend Reason = case(user_count > user_threshold and folder_count > folder_threshold, \"Both User and Folder Threshold Exceeded\", folder_count > folder_threshold and user_count < user_threshold, \"Folder Count Threshold Exceeded\", \"User Threshold Exceeded\")\n | sort by user_count desc\n | project-reorder UserId, user_count, folder_count, set_MailboxOwnerUPN, set_ClientIP, set_ClientInfoString, set_folders\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by UserId, ClientIP;\n// Final Output\nCombinedEvents\n | project UserId, user_count, folder_count, set_MailboxOwnerUPN, set_ClientIP, set_ClientInfoString, set_folders, AccountName, AccountUPNSuffix\n | order by user_count desc\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "Looks for users accessing multiple other users' mailboxes or accessing multiple folders in another users mailbox." - }, - { - "name": "tactics", - "value": "Collection" - }, - { - "name": "techniques", - "value": "T1114.002" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject1')._huntingQuerycontentId1),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 1", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject1')._huntingQuerycontentId1)]", - "contentId": "[variables('huntingQueryObject1')._huntingQuerycontentId1]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject1').huntingQueryVersion1]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject1')._huntingQuerycontentId1]", - "contentKind": "HuntingQuery", - "displayName": "GSA Enriched Office 365 - Anomalous access to other users' mailboxes", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject1')._huntingQuerycontentId1,'-', '2.0.2')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject1')._huntingQuerycontentId1,'-', '2.0.2')))]", - "version": "2.0.2" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject2').huntingQueryTemplateSpecName2]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "ExternalUserAddedRemovedInTeams_HuntVersion_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject2').huntingQueryVersion2]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_2", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "External User Added and Removed in a Short Timeframe", - "category": "Hunting Queries", - "query": "// If you want to look at user added further than 7 days ago adjust this value\n// If you want to change the timeframe of how quickly accounts need to be added and removed change this value\nlet time_delta = 1h;\nEnrichedMicrosoft365AuditLogs\n| where Workload == \"MicrosoftTeams\"\n| where Operation == \"MemberAdded\"\n| extend UPN = tostring(parse_json(tostring(AdditionalProperties)).UPN) // Assuming UPN is stored in AdditionalProperties\n| where UPN contains \"#EXT#\"\n| project TimeAdded = TimeGenerated, Operation, UPN, UserWhoAdded = UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName), TeamGuid = tostring(parse_json(tostring(AdditionalProperties)).TeamGuid)\n| join kind=innerunique (\n EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"MemberRemoved\"\n | extend UPN = tostring(parse_json(tostring(AdditionalProperties)).UPN) // Assuming UPN is stored in AdditionalProperties\n | where UPN contains \"#EXT#\"\n | project TimeDeleted = TimeGenerated, Operation, UPN, UserWhoDeleted = UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName), TeamGuid = tostring(parse_json(tostring(AdditionalProperties)).TeamGuid)\n) on UPN, TeamGuid\n| where TimeDeleted < (TimeAdded + time_delta)\n| project TimeAdded, TimeDeleted, UPN, UserWhoAdded, UserWhoDeleted, TeamName, TeamGuid\n| extend AccountName = tostring(split(UPN, \"@\")[0]), AccountUPNSuffix = tostring(split(UPN, \"@\")[1])\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "This hunting query identifies external user accounts that are added to a Team and then removed within one hour." - }, - { - "name": "tactics", - "value": "Persistence" - }, - { - "name": "techniques", - "value": "T1136" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject2')._huntingQuerycontentId2),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 2", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject2')._huntingQuerycontentId2)]", - "contentId": "[variables('huntingQueryObject2')._huntingQuerycontentId2]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject2').huntingQueryVersion2]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject2')._huntingQuerycontentId2]", - "contentKind": "HuntingQuery", - "displayName": "External User Added and Removed in a Short Timeframe", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject2')._huntingQuerycontentId2,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject2')._huntingQuerycontentId2,'-', '2.0.1')))]", - "version": "2.0.1" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject3').huntingQueryTemplateSpecName3]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "ExternalUserFromNewOrgAddedToTeams_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject3').huntingQueryVersion3]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_3", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "GSA Enriched Office 365 - External user from a new organisation added to Teams", - "category": "Hunting Queries", - "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = totimespan((endtime - starttime) * 7);\n// OfficeActivity Known Organizations\nlet known_orgs_office = (\n OfficeActivity\n | where TimeGenerated between(ago(lookback)..starttime)\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberAdded\" or Operation =~ \"TeamsSessionStarted\"\n | extend UPN = iif(Operation == \"MemberAdded\", tostring(Members[0].UPN), UserId)\n | extend Organization = tostring(split(split(UPN, \"_\")[1], \"#\")[0])\n | where isnotempty(Organization)\n | summarize by Organization\n);\n// OfficeActivity Query for New Organizations\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated between(starttime..endtime)\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberAdded\"\n | extend UPN = tostring(parse_json(Members)[0].UPN)\n | extend Organization = tostring(split(split(UPN, \"_\")[1], \"#\")[0])\n | where isnotempty(Organization)\n | where Organization !in (known_orgs_office)\n | extend AccountName = tostring(split(UPN, \"@\")[0]), AccountUPNSuffix = tostring(split(UPN, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n// EnrichedMicrosoft365AuditLogs Known Organizations\nlet known_orgs_enriched = (\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between(ago(lookback)..starttime)\n | where Workload == \"MicrosoftTeams\"\n | where Operation in (\"MemberAdded\", \"TeamsSessionStarted\")\n | extend Members = parse_json(tostring(AdditionalProperties.Members))\n | extend UPN = iif(Operation == \"MemberAdded\", tostring(Members[0].UPN), UserId)\n | extend Organization = tostring(split(split(UPN, \"_\")[1], \"#\")[0])\n | where isnotempty(Organization)\n | summarize by Organization\n);\n// EnrichedMicrosoft365AuditLogs Query for New Organizations\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between(starttime..endtime)\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"MemberAdded\"\n | extend Members = parse_json(tostring(AdditionalProperties.Members))\n | extend UPN = tostring(Members[0].UPN)\n | extend Organization = tostring(split(split(UPN, \"_\")[1], \"#\")[0])\n | where isnotempty(Organization)\n | where Organization !in (known_orgs_enriched)\n | extend AccountName = tostring(split(UPN, \"@\")[0]), AccountUPNSuffix = tostring(split(UPN, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeGenerated, *) by Organization, UPN;\n// Final Output\nCombinedEvents\n | project Organization, UPN, AccountName, AccountUPNSuffix\n | order by Organization asc\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "This query identifies external users added to Teams where the user's domain is not one previously seen in Teams data." - }, - { - "name": "tactics", - "value": "Persistence" - }, - { - "name": "techniques", - "value": "T1136" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject3')._huntingQuerycontentId3),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 3", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject3')._huntingQuerycontentId3)]", - "contentId": "[variables('huntingQueryObject3')._huntingQuerycontentId3]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject3').huntingQueryVersion3]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject3')._huntingQuerycontentId3]", - "contentKind": "HuntingQuery", - "displayName": "GSA Enriched Office 365 - External user from a new organisation added to Teams", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject3')._huntingQuerycontentId3,'-', '2.0.2')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject3')._huntingQuerycontentId3,'-', '2.0.2')))]", - "version": "2.0.2" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject4').huntingQueryTemplateSpecName4]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "Mail_redirect_via_ExO_transport_rule_hunting_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject4').huntingQueryVersion4]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_4", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule", - "category": "Hunting Queries", - "query": "// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload == \"Exchange\"\n | where Operation in (\"New-TransportRule\", \"Set-TransportRule\")\n | mv-apply DynamicParameters = todynamic(AdditionalProperties.Parameters) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | extend RuleName = case(\n Operation == \"Set-TransportRule\", ObjectId,\n Operation == \"New-TransportRule\", ParsedParameters.Name,\n \"Unknown\"\n )\n | mv-expand ExpandedParameters = todynamic(AdditionalProperties.Parameters)\n | where ExpandedParameters.Name in (\"BlindCopyTo\", \"RedirectMessageTo\") and isnotempty(ExpandedParameters.Value)\n | extend RedirectTo = ExpandedParameters.Value\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIp)[0]\n | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, Operation, RuleName, AdditionalProperties\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload == \"Exchange\"\n | where Operation in (\"New-TransportRule\", \"Set-TransportRule\")\n | mv-apply DynamicParameters = todynamic(Parameters) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | extend RuleName = case(\n Operation == \"Set-TransportRule\", OfficeObjectId,\n Operation == \"New-TransportRule\", ParsedParameters.Name,\n \"Unknown\"\n )\n | mv-expand ExpandedParameters = todynamic(Parameters)\n | where ExpandedParameters.Name in (\"BlindCopyTo\", \"RedirectMessageTo\") and isnotempty(ExpandedParameters.Value)\n | extend RedirectTo = ExpandedParameters.Value\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIP)[0]\n | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, Operation, RuleName, Parameters\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, IP_0_Address = IPAddress;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeGenerated, *) by RuleName, UserId;\n// Final Output\nCombinedEvents\n | project TimeGenerated, RuleName, RedirectTo, IPAddress, Port, UserId, AccountName, AccountUPNSuffix\n | order by TimeGenerated desc\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "Identifies when Exchange Online transport rule is configured to forward emails.\nThis could be an adversary mailbox configured to collect mail from multiple user accounts." - }, - { - "name": "tactics", - "value": "Collection,Exfiltration" - }, - { - "name": "techniques", - "value": "T1114,T1020" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject4')._huntingQuerycontentId4),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 4", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject4')._huntingQuerycontentId4)]", - "contentId": "[variables('huntingQueryObject4')._huntingQuerycontentId4]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject4').huntingQueryVersion4]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject4')._huntingQuerycontentId4]", - "contentKind": "HuntingQuery", - "displayName": "GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject4')._huntingQuerycontentId4,'-', '2.0.2')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject4')._huntingQuerycontentId4,'-', '2.0.2')))]", - "version": "2.0.2" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject5').huntingQueryTemplateSpecName5]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "MultiTeamBot_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject5').huntingQueryVersion5]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_5", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "GSA Enriched Office 365 - Bots added to multiple teams", - "category": "Hunting Queries", - "query": "let threshold = 2; // Adjust this threshold based on your environment\nlet time_threshold = timespan(5m); // Adjust the time delta threshold\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"BotAddedToTeam\"\n | summarize Start = max(TimeGenerated), End = min(TimeGenerated), Teams = make_set(TeamName, 10000) by UserId\n | extend CountOfTeams = array_length(Teams)\n | extend TimeDelta = End - Start\n | where CountOfTeams > threshold\n | where TimeDelta >= time_threshold\n | project Start, End, Teams, CountOfTeams, UserId\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"BotAddedToTeam\"\n | extend TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n | summarize Start = max(TimeGenerated), End = min(TimeGenerated), Teams = make_set(TeamName, 10000) by UserId\n | extend CountOfTeams = array_length(Teams)\n | extend TimeDelta = End - Start\n | where CountOfTeams > threshold\n | where TimeDelta <= time_threshold\n | project Start, End, Teams, CountOfTeams, UserId\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(Start, *) by UserId;\n// Final Output\nCombinedEvents\n | project Start, End, Teams, CountOfTeams, UserId, AccountName, AccountUPNSuffix\n | order by Start desc\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "This hunting query helps identify bots added to multiple Teams in a short space of time." - }, - { - "name": "tactics", - "value": "Persistence,Collection" - }, - { - "name": "techniques", - "value": "T1176,T1119" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject5')._huntingQuerycontentId5),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 5", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject5')._huntingQuerycontentId5)]", - "contentId": "[variables('huntingQueryObject5')._huntingQuerycontentId5]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject5').huntingQueryVersion5]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject5')._huntingQuerycontentId5]", - "contentKind": "HuntingQuery", - "displayName": "GSA Enriched Office 365 - Bots added to multiple teams", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject5')._huntingQuerycontentId5,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject5')._huntingQuerycontentId5,'-', '2.0.1')))]", - "version": "2.0.1" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject6').huntingQueryTemplateSpecName6]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "MultiTeamOwner_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject6').huntingQueryVersion6]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_6", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "GSA Enriched Office 365 - User made Owner of multiple teams", - "category": "Hunting Queries", - "query": "let max_owner_count = 3; \n// Adjust this value to change how many teams a user is made owner of before detecting\n// OfficeActivity Query: Identify users who have been made owners of more than 'max_owner_count' teams\nlet high_owner_count_office = (\n OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberRoleChanged\"\n | extend Member = tostring(parse_json(Members)[0].UPN) \n | extend NewRole = toint(parse_json(Members)[0].Role)\n | where NewRole == 2 // Role 2 corresponds to \"Owner\"\n | summarize TeamCount = dcount(TeamName) by Member\n | where TeamCount > max_owner_count\n | project Member\n);\n// OfficeActivity Query: Fetch details for users with high ownership count\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberRoleChanged\"\n | extend Member = tostring(parse_json(Members)[0].UPN)\n | extend NewRole = toint(parse_json(Members)[0].Role)\n | where NewRole == 2 // Role 2 corresponds to \"Owner\"\n | where Member in (high_owner_count_office)\n | extend AccountName = tostring(split(Member, \"@\")[0]), AccountUPNSuffix = tostring(split(Member, \"@\")[1]);\n// EnrichedMicrosoft365AuditLogs Query: Identify users who have been made owners of more than 'max_owner_count' teams\nlet high_owner_count_enriched = (\n EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"MemberRoleChanged\"\n | extend Member = tostring(UserId)\n | extend NewRole = toint(parse_json(tostring(AdditionalProperties)).Role)\n | where NewRole == 2 // Role 2 corresponds to \"Owner\"\n | summarize TeamCount = dcount(ObjectId) by Member\n | where TeamCount > max_owner_count\n | project Member\n);\n// EnrichedMicrosoft365AuditLogs Query: Fetch details for users with high ownership count\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"MemberRoleChanged\"\n | extend Member = tostring(UserId)\n | extend NewRole = toint(parse_json(tostring(AdditionalProperties)).Role)\n | where NewRole == 2 // Role 2 corresponds to \"Owner\"\n | where Member in (high_owner_count_enriched)\n | extend AccountName = tostring(split(Member, \"@\")[0]), AccountUPNSuffix = tostring(split(Member, \"@\")[1]);\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize by Member, AccountName, AccountUPNSuffix;\n// Final Output\nCombinedEvents\n| order by Member asc\n| project Member, AccountName, AccountUPNSuffix\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "This hunting query identifies users who have been made Owner of multiple Teams." - }, - { - "name": "tactics", - "value": "PrivilegeEscalation" - }, - { - "name": "techniques", - "value": "T1078" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject6')._huntingQuerycontentId6),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 6", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject6')._huntingQuerycontentId6)]", - "contentId": "[variables('huntingQueryObject6')._huntingQuerycontentId6]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject6').huntingQueryVersion6]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject6')._huntingQuerycontentId6]", - "contentKind": "HuntingQuery", - "displayName": "GSA Enriched Office 365 - User made Owner of multiple teams", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject6')._huntingQuerycontentId6,'-', '2.0.2')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject6')._huntingQuerycontentId6,'-', '2.0.2')))]", - "version": "2.0.2" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject7').huntingQueryTemplateSpecName7]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "MultipleTeamsDeletes_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject7').huntingQueryVersion7]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_7", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "GSA Enriched Office 365 - Multiple Teams deleted by a single user", - "category": "Hunting Queries", - "query": "let max_delete = 3; // Adjust this value to change how many Teams should be deleted before being included\n// EnrichedMicrosoft365AuditLogs - Users who deleted more than 'max_delete' Teams\nlet deleting_users_enriched = (\n EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"TeamDeleted\"\n | summarize count_ = count() by UserId\n | where count_ > max_delete\n | project UserId\n);\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"TeamDeleted\"\n | where UserId in (deleting_users_enriched)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix\n | project TimeGenerated, UserId, Account_0_Name, Account_0_UPNSuffix;\n// OfficeActivity - Users who deleted more than 'max_delete' Teams\nlet deleting_users_office = (\n OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"TeamDeleted\"\n | summarize count_ = count() by UserId\n | where count_ > max_delete\n | project UserId\n);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"TeamDeleted\"\n | where UserId in (deleting_users_office)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix\n | project TimeGenerated, UserId, Account_0_Name, Account_0_UPNSuffix;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeGenerated, *) by UserId;\n// Final Output\nCombinedEvents\n| project TimeGenerated, UserId, Account_0_Name, Account_0_UPNSuffix\n| order by TimeGenerated desc\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "This hunting query identifies where multiple Teams have been deleted by a single user in a short timeframe." - }, - { - "name": "tactics", - "value": "Impact" - }, - { - "name": "techniques", - "value": "T1485,T1489" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject7')._huntingQuerycontentId7),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 7", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject7')._huntingQuerycontentId7)]", - "contentId": "[variables('huntingQueryObject7')._huntingQuerycontentId7]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject7').huntingQueryVersion7]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject7')._huntingQuerycontentId7]", - "contentKind": "HuntingQuery", - "displayName": "GSA Enriched Office 365 - Multiple Teams deleted by a single user", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject7')._huntingQuerycontentId7,'-', '2.0.2')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject7')._huntingQuerycontentId7,'-', '2.0.2')))]", - "version": "2.0.2" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject8').huntingQueryTemplateSpecName8]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "NewBotAddedToTeams_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject8').huntingQueryVersion8]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_8", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "GSA Enriched Office 365 - Previously Unseen Bot or Application Added to Teams", - "category": "Hunting Queries", - "query": "let starttime = todatetime('{{StartTimeISO}}');\n let endtime = todatetime('{{EndTimeISO}}');\n let lookback = starttime - 14d;\n // Historical bots from EnrichedMicrosoft365AuditLogs\n let historical_bots_enriched = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (lookback .. starttime)\n | where Workload == \"MicrosoftTeams\"\n | extend AddonName = tostring(parse_json(tostring(AdditionalProperties)).AddonName)\n | where isnotempty(AddonName)\n | distinct AddonName;\n // Historical bots from OfficeActivity\n let historical_bots_office = OfficeActivity\n | where TimeGenerated between (lookback .. starttime)\n | where OfficeWorkload == \"MicrosoftTeams\"\n | where isnotempty(AddonName)\n | distinct AddonName;\n // Find new bots in Enriched Logs\n let new_bots_enriched = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where Workload == \"MicrosoftTeams\"\n | extend AddonName = tostring(parse_json(tostring(AdditionalProperties)).AddonName)\n | where AddonName !in (historical_bots_enriched)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n // Find new bots in OfficeActivity\n let new_bots_office = OfficeActivity\n | where TimeGenerated between (starttime .. endtime)\n | where OfficeWorkload == \"MicrosoftTeams\"\n | where isnotempty(AddonName)\n | where AddonName !in (historical_bots_office)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n // Combine both new bots from Enriched Logs and OfficeActivity\n let CombinedNewBots = new_bots_enriched\n | union new_bots_office\n | summarize arg_min(TimeGenerated, *) by AddonName, UserId;\n // Final output\n CombinedNewBots\n | project TimeGenerated, AddonName, UserId, AccountName, AccountUPNSuffix\n | order by TimeGenerated desc\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "This hunting query helps identify new, and potentially unapproved applications or bots being added to Teams." - }, - { - "name": "tactics", - "value": "Persistence,Collection" - }, - { - "name": "techniques", - "value": "T1176,T1119" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject8')._huntingQuerycontentId8),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 8", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject8')._huntingQuerycontentId8)]", - "contentId": "[variables('huntingQueryObject8')._huntingQuerycontentId8]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject8').huntingQueryVersion8]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject8')._huntingQuerycontentId8]", - "contentKind": "HuntingQuery", - "displayName": "GSA Enriched Office 365 - Previously Unseen Bot or Application Added to Teams", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject8')._huntingQuerycontentId8,'-', '2.0.2')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject8')._huntingQuerycontentId8,'-', '2.0.2')))]", - "version": "2.0.2" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject9').huntingQueryTemplateSpecName9]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "New_WindowsReservedFileNamesOnOfficeFileServices_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject9').huntingQueryVersion9]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_9", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "GSA Enriched Office 365 - New Windows Reserved Filenames staged on Office file services", - "category": "Hunting Queries", - "query": "let starttime = todatetime('{{StartTimeISO}}');\n let endtime = todatetime('{{EndTimeISO}}');\n let lookback = totimespan((endtime - starttime) * 7);\n \n // Reserved file names and extensions for Windows\n let Reserved = dynamic(['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']);\n \n // EnrichedMicrosoft365AuditLogs Query\n let EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | extend FileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | extend ClientUserAgent = tostring(parse_json(tostring(AdditionalProperties)).ClientUserAgent)\n | extend SiteUrl = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(ObjectId)\n | where ObjectId !~ FileName\n | where ObjectId in (Reserved) or FileName in (Reserved)\n | where ClientUserAgent !has \"Mac OS\"\n | join kind=leftanti (\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (ago(lookback) .. starttime)\n | extend FileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | extend ClientUserAgent = tostring(parse_json(tostring(AdditionalProperties)).ClientUserAgent)\n | extend SiteUrl = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(ObjectId)\n | where ObjectId !~ FileName\n | where ObjectId in (Reserved) or FileName in (Reserved)\n | where ClientUserAgent !has \"Mac OS\"\n | summarize PrevSeenCount = count() by ObjectId, UserId, FileName\n ) on ObjectId\n | extend SiteUrlUserFolder = tolower(split(SiteUrl, '/')[-2])\n | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n | extend UserIdDiffThanUserFolder = iff(SiteUrl has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(ClientUserAgent, 100000), Ids = make_list(Id, 100000),\n SourceRelativeUrls = make_list(ObjectId, 100000), FileNames = make_list(FileName, 100000) by Workload, RecordType, UserType, UserKey, UserId, ClientIp, SiteUrl, ObjectId, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIp\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, URL_0_Url = SiteUrl;\n // OfficeActivity Query\n let OfficeEvents = OfficeActivity\n | where TimeGenerated between (starttime .. endtime)\n | where isnotempty(SourceFileExtension)\n | where SourceFileName !~ SourceFileExtension\n | where SourceFileExtension in (Reserved) or SourceFileName in (Reserved)\n | where UserAgent !has \"Mac OS\"\n | join kind=leftanti (\n OfficeActivity\n | where TimeGenerated between (ago(lookback) .. starttime)\n | where isnotempty(SourceFileExtension)\n | where SourceFileName !~ SourceFileExtension\n | where SourceFileExtension in (Reserved) or SourceFileName in (Reserved)\n | where UserAgent !has \"Mac OS\"\n | summarize PrevSeenCount = count() by SourceFileExtension\n ) on SourceFileExtension\n | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])\n | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(UserAgent, 100000), OfficeIds = make_list(OfficeId, 100000),\n SourceRelativeUrls = make_list(SourceRelativeUrl, 100000), FileNames = make_list(SourceFileName, 100000) by OfficeWorkload, RecordType, UserType, UserKey, UserId, ClientIP, Site_Url, SourceFileExtension, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIP\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, URL_0_Url = Site_Url;\n // Combine Office and Enriched Logs\n let CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by UserId, ClientIP;\n // Final Output\n CombinedEvents\n | project StartTime, EndTime, Operations, UserAgents, IP_0_Address, Account_0_Name, Account_0_UPNSuffix, URL_0_Url\n | order by StartTime desc\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "This identifies new Windows Reserved Filenames on Office services like SharePoint and OneDrive in the past 7 days. It also detects when a user uploads these files to another user's workspace, which may indicate malicious activity." - }, - { - "name": "tactics", - "value": "CommandAndControl" - }, - { - "name": "techniques", - "value": "T1105" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject9')._huntingQuerycontentId9),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 9", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject9')._huntingQuerycontentId9)]", - "contentId": "[variables('huntingQueryObject9')._huntingQuerycontentId9]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject9').huntingQueryVersion9]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject9')._huntingQuerycontentId9]", - "contentKind": "HuntingQuery", - "displayName": "GSA Enriched Office 365 - New Windows Reserved Filenames staged on Office file services", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject9')._huntingQuerycontentId9,'-', '2.0.2')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject9')._huntingQuerycontentId9,'-', '2.0.2')))]", - "version": "2.0.2" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject10').huntingQueryTemplateSpecName10]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "OfficeMailForwarding_hunting_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject10').huntingQueryVersion10]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_10", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "GSA Enriched Office 365 - Office Mail Forwarding - Hunting Version", - "category": "Hunting Queries", - "query": "let starttime = todatetime('{{StartTimeISO}}');\n let endtime = todatetime('{{EndTimeISO}}');\n \n // Enriched Logs Query for forwarding rule operations\n let EnrichedForwardRules = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where Workload == \"Exchange\"\n | where (Operation == \"Set-Mailbox\" and tostring(parse_json(tostring(AdditionalProperties))) contains 'ForwardingSmtpAddress') \n or (Operation in ('New-InboxRule', 'Set-InboxRule') and (tostring(parse_json(tostring(AdditionalProperties))) contains 'ForwardTo' or tostring(parse_json(tostring(AdditionalProperties))) contains 'RedirectTo'))\n | extend parsed = parse_json(tostring(AdditionalProperties))\n | extend fwdingDestination_initial = iif(Operation == \"Set-Mailbox\", tostring(parsed.ForwardingSmtpAddress), coalesce(tostring(parsed.ForwardTo), tostring(parsed.RedirectTo)))\n | where isnotempty(fwdingDestination_initial)\n | extend fwdingDestination = iff(fwdingDestination_initial has \"smtp\", (split(fwdingDestination_initial, \":\")[1]), fwdingDestination_initial)\n | parse fwdingDestination with * '@' ForwardedtoDomain \n | parse UserId with * '@' UserDomain\n | extend subDomain = ((split(strcat(tostring(split(UserDomain, '.')[-2]), '.', tostring(split(UserDomain, '.')[-1])), '.'))[0])\n | where ForwardedtoDomain !contains subDomain\n | extend Result = iff(ForwardedtoDomain != UserDomain, \"Mailbox rule created to forward to External Domain\", \"Forward rule for Internal domain\")\n | extend ClientIPAddress = case(ClientIp has \".\", tostring(split(ClientIp, \":\")[0]), ClientIp has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIp, \"]\")[0]))), ClientIp)\n | extend Port = case(ClientIp has \".\", (split(ClientIp, \":\")[1]), ClientIp has \"[\", tostring(split(ClientIp, \"]:\")[1]), ClientIp)\n | extend Host = tostring(parse_json(tostring(AdditionalProperties)).OriginatingServer)\n | extend HostName = tostring(split(Host, \".\")[0])\n | extend DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))\n | project TimeGenerated, UserId, UserDomain, subDomain, Operation, ForwardedtoDomain, ClientIPAddress, Result, Port, ObjectId, fwdingDestination, HostName, DnsDomain\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIPAddress, Host_0_HostName = HostName, Host_0_DnsDomain = DnsDomain, Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n // Office Activity Query for forwarding rule operations\n let OfficeForwardRules = OfficeActivity\n | where TimeGenerated between (starttime .. endtime)\n | where OfficeWorkload == \"Exchange\"\n | where (Operation =~ \"Set-Mailbox\" and Parameters contains 'ForwardingSmtpAddress') \n or (Operation in~ ('New-InboxRule', 'Set-InboxRule') and (Parameters contains 'ForwardTo' or Parameters contains 'RedirectTo'))\n | extend parsed = parse_json(Parameters)\n | extend fwdingDestination_initial = (iif(Operation =~ \"Set-Mailbox\", tostring(parsed[1].Value), tostring(parsed[2].Value)))\n | where isnotempty(fwdingDestination_initial)\n | extend fwdingDestination = iff(fwdingDestination_initial has \"smtp\", (split(fwdingDestination_initial, \":\")[1]), fwdingDestination_initial)\n | parse fwdingDestination with * '@' ForwardedtoDomain \n | parse UserId with * '@' UserDomain\n | extend subDomain = ((split(strcat(tostring(split(UserDomain, '.')[-2]), '.', tostring(split(UserDomain, '.')[-1])), '.') [0]))\n | where ForwardedtoDomain !contains subDomain\n | extend Result = iff(ForwardedtoDomain != UserDomain, \"Mailbox rule created to forward to External Domain\", \"Forward rule for Internal domain\")\n | extend ClientIPAddress = case(ClientIP has \".\", tostring(split(ClientIP, \":\")[0]), ClientIP has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIP, \"]\")[0]))), ClientIP)\n | extend Port = case(ClientIP has \".\", (split(ClientIP, \":\")[1]), ClientIP has \"[\", tostring(split(ClientIP, \"]:\")[1]), ClientIP)\n | extend Host = tostring(split(OriginatingServer, \" (\")[0])\n | extend HostName = tostring(split(Host, \".\")[0])\n | extend DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))\n | project TimeGenerated, UserId, UserDomain, subDomain, Operation, ForwardedtoDomain, ClientIPAddress, Result, Port, HostName, DnsDomain\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIPAddress, Host_0_HostName = HostName, Host_0_DnsDomain = DnsDomain, Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n // Combine the results from both Enriched and Office Activity logs\n let CombinedForwardRules = EnrichedForwardRules\n | union OfficeForwardRules\n | summarize arg_min(TimeGenerated, *) by UserId, ForwardedtoDomain, Operation\n | project TimeGenerated, UserId, UserDomain, subDomain, Operation, ForwardedtoDomain, ClientIPAddress, Result, Port, ObjectId, fwdingDestination, Host_0_HostName, Host_0_DnsDomain, IP_0_Address, Account_0_Name, Account_0_UPNSuffix; \n // Final output\n CombinedForwardRules\n | order by TimeGenerated desc;\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "Adversaries often abuse email-forwarding rules to monitor victim activities, steal information, and gain intelligence on the victim or their organization. This query highlights cases where user mail is being forwarded, including to external domains." - }, - { - "name": "tactics", - "value": "Collection,Exfiltration" - }, - { - "name": "techniques", - "value": "T1114,T1020" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject10')._huntingQuerycontentId10),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 10", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject10')._huntingQuerycontentId10)]", - "contentId": "[variables('huntingQueryObject10')._huntingQuerycontentId10]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject10').huntingQueryVersion10]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject10')._huntingQuerycontentId10]", - "contentKind": "HuntingQuery", - "displayName": "GSA Enriched Office 365 - Office Mail Forwarding - Hunting Version", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject10')._huntingQuerycontentId10,'-', '2.0.2')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject10')._huntingQuerycontentId10,'-', '2.0.2')))]", - "version": "2.0.2" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject11').huntingQueryTemplateSpecName11]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "TeamsFilesUploaded_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject11').huntingQueryVersion11]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_11", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "GSA Enriched Office 365 - Files uploaded to teams and access summary", - "category": "Hunting Queries", - "query": "// Define the query for EnrichedMicrosoft365AuditLogs\nlet enrichedLogs = EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation == \"FileUploaded\"\n | where UserId != \"app@sharepoint\"\n | where ObjectId has \"Microsoft Teams Chat Files\"\n | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | join kind=leftouter (\n EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileAccessed\")\n | where UserId != \"app@sharepoint\"\n | where ObjectId has \"Microsoft Teams Chat Files\"\n | extend UserId1 = UserId, ClientIp1 = ClientIp\n ) on ObjectId\n | extend userBag = bag_pack(\"UserId1\", UserId1, \"ClientIp1\", ClientIp1)\n | summarize AccessedBy = make_bag(userBag), make_set(UserId1, 10000) by bin(TimeGenerated, 1h), UserId, ObjectId, SourceFileName\n | extend NumberOfUsersAccessed = array_length(bag_keys(AccessedBy))\n | project timestamp = TimeGenerated, UserId, FileLocation = ObjectId, FileName = SourceFileName, AccessedBy, NumberOfUsersAccessed\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix;\n// Define the query for OfficeActivity\nlet officeLogs = OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where Operation == \"FileUploaded\"\n | where UserId != \"app@sharepoint\"\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | join kind=leftouter (\n OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileAccessed\")\n | where UserId != \"app@sharepoint\"\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n ) on OfficeObjectId\n | extend userBag = bag_pack(UserId1, ClientIP1)\n | summarize AccessedBy = make_bag(userBag, 10000), make_set(UserId1, 10000) by TimeGenerated, UserId, OfficeObjectId, SourceFileName\n | extend NumberUsers = array_length(bag_keys(AccessedBy))\n | project timestamp = TimeGenerated, UserId, FileLocation = OfficeObjectId, FileName = SourceFileName, AccessedBy, NumberOfUsersAccessed = NumberUsers\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix;\n// Union both results\nenrichedLogs\n| union officeLogs\n| order by timestamp desc;\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "This hunting query identifies files uploaded to SharePoint via a Teams chat and\nsummarizes users and IP addresses that have accessed these files. This allows for \nidentification of anomalous file sharing patterns." - }, - { - "name": "tactics", - "value": "InitialAccess,Exfiltration" - }, - { - "name": "techniques", - "value": "T1199,T1102,T1078" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject11')._huntingQuerycontentId11),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 11", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject11')._huntingQuerycontentId11)]", - "contentId": "[variables('huntingQueryObject11')._huntingQuerycontentId11]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject11').huntingQueryVersion11]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject11')._huntingQuerycontentId11]", - "contentKind": "HuntingQuery", - "displayName": "GSA Enriched Office 365 - Files uploaded to teams and access summary", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject11')._huntingQuerycontentId11,'-', '2.0.2')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject11')._huntingQuerycontentId11,'-', '2.0.2')))]", - "version": "2.0.2" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject12').huntingQueryTemplateSpecName12]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "UserAddToTeamsAndUploadsFile_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject12').huntingQueryVersion12]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_12", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "GSA Enriched Office 365 - User added to Teams and immediately uploads file", - "category": "Hunting Queries", - "query": "let threshold = 1m;\n // Define MemberAddedEvents for EnrichedMicrosoft365AuditLogs\n let MemberAddedEvents_Enriched = EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"MemberAdded\"\n | extend TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n | project TimeGenerated, UploaderID = UserId, TeamName;\n // Define FileUploadEvents for EnrichedMicrosoft365AuditLogs\n let FileUploadEvents_Enriched = EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where ObjectId has \"Microsoft Teams Chat Files\"\n | where Operation == \"FileUploaded\"\n | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | project UploadTime = TimeGenerated, UploaderID = UserId, FileLocation = ObjectId, SourceFileName;\n // Perform join for EnrichedMicrosoft365AuditLogs\n let EnrichedResults = MemberAddedEvents_Enriched\n | join kind=inner (FileUploadEvents_Enriched) on UploaderID\n | where UploadTime > TimeGenerated and UploadTime < TimeGenerated + threshold\n | extend timestamp = TimeGenerated, AccountCustomEntity = UploaderID;\n // Define MemberAddedEvents for OfficeActivity\n let MemberAddedEvents_Office = OfficeActivity\n | where OfficeWorkload == \"MicrosoftTeams\"\n | where Operation == \"MemberAdded\"\n | extend TeamName = iff(isempty(TeamName), Members[0].UPN, TeamName)\n | project TimeGenerated, UploaderID = UserId, TeamName;\n // Define FileUploadEvents for OfficeActivity\n let FileUploadEvents_Office = OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | where Operation == \"FileUploaded\"\n | project UploadTime = TimeGenerated, UploaderID = UserId, FileLocation = OfficeObjectId, FileName = SourceFileName;\n // Perform join for OfficeActivity\n let OfficeResults = MemberAddedEvents_Office\n | join kind=inner (FileUploadEvents_Office) on UploaderID\n | where UploadTime > TimeGenerated and UploadTime < TimeGenerated + threshold\n | project-away UploaderID1\n | extend timestamp = TimeGenerated, AccountCustomEntity = UploaderID;\n // Union both results\n EnrichedResults\n | union OfficeResults\n | order by timestamp desc;\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "This hunting query identifies users who are added to a Teams Channel or Teams chat\nand within 1 minute of being added upload a file via the chat. This might be\nan indicator of suspicious activity." - }, - { - "name": "tactics", - "value": "InitialAccess" - }, - { - "name": "techniques", - "value": "T1566" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject12')._huntingQuerycontentId12),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 12", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject12')._huntingQuerycontentId12)]", - "contentId": "[variables('huntingQueryObject12')._huntingQuerycontentId12]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject12').huntingQueryVersion12]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject12')._huntingQuerycontentId12]", - "contentKind": "HuntingQuery", - "displayName": "GSA Enriched Office 365 - User added to Teams and immediately uploads file", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject12')._huntingQuerycontentId12,'-', '2.0.2')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject12')._huntingQuerycontentId12,'-', '2.0.2')))]", - "version": "2.0.2" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject13').huntingQueryTemplateSpecName13]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "WindowsReservedFileNamesOnOfficeFileServices_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject13').huntingQueryVersion13]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_13", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "GSA Enriched Office 365 - Windows Reserved Filenames Staged on Office File Services", - "category": "Hunting Queries", - "query": "let Reserved = dynamic(['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']);\n // Query for OfficeActivity\n let OfficeActivityResults = OfficeActivity\n | where isnotempty(SourceFileExtension)\n | where SourceFileExtension in (Reserved) or SourceFileName in (Reserved)\n | where UserAgent !has \"Mac OS\"\n | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])\n | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(UserAgent, 100000), OfficeIds = make_list(OfficeId, 100000), SourceRelativeUrls = make_list(SourceRelativeUrl, 100000), FileNames = make_list(SourceFileName, 100000)\n by OfficeWorkload, RecordType, UserType, UserKey, UserId, ClientIP, Site_Url, SourceFileExtension, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIP\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix\n | extend URL_0_Url = Site_Url;\n // Query for EnrichedMicrosoft365AuditLogs\n let EnrichedMicrosoft365Results = EnrichedMicrosoft365AuditLogs\n | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(ObjectId)\n | where ObjectId in (Reserved) or SourceFileName in (Reserved)\n | where UserAgent !has \"Mac OS\"\n | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])\n | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(UserAgent, 100000), ObjectIds = make_list(Id, 100000), SourceRelativeUrls = make_list(ObjectId, 100000), FileNames = make_list(SourceFileName, 100000)\n by Workload, RecordType, UserType, UserKey, UserId, ClientIp, Site_Url, ObjectId, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIp\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix\n | extend URL_0_Url = Site_Url;\n // Combine both queries\n OfficeActivityResults\n | union EnrichedMicrosoft365Results\n | order by StartTime desc;\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "This identifies Windows Reserved Filenames on Office services like SharePoint and OneDrive. It also detects when a user uploads these files to another user's workspace, which may indicate malicious activity." - }, - { - "name": "tactics", - "value": "CommandAndControl" - }, - { - "name": "techniques", - "value": "T1105" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject13')._huntingQuerycontentId13),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 13", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject13')._huntingQuerycontentId13)]", - "contentId": "[variables('huntingQueryObject13')._huntingQuerycontentId13]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject13').huntingQueryVersion13]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject13')._huntingQuerycontentId13]", - "contentKind": "HuntingQuery", - "displayName": "GSA Enriched Office 365 - Windows Reserved Filenames Staged on Office File Services", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject13')._huntingQuerycontentId13,'-', '2.0.2')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject13')._huntingQuerycontentId13,'-', '2.0.2')))]", - "version": "2.0.2" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject14').huntingQueryTemplateSpecName14]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "double_file_ext_exes_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject14').huntingQueryVersion14]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_14", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "Exes with double file extension and access summary", - "category": "Hunting Queries", - "query": "let known_ext = dynamic([\"lnk\", \"log\", \"option\", \"config\", \"manifest\", \"partial\"]);\nlet excluded_users = dynamic([\"app@sharepoint\"]);\nEnrichedMicrosoft365AuditLogs\n| where RecordType == \"SharePointFileOperation\" and isnotempty(ObjectId)\n| where ObjectId has \".exe.\" \n and not(ObjectId endswith \".lnk\") \n and not(ObjectId endswith \".log\") \n and not(ObjectId endswith \".option\") \n and not(ObjectId endswith \".config\") \n and not(ObjectId endswith \".manifest\") \n and not(ObjectId endswith \".partial\")\n| extend Extension = extract(\"[^.]*\\\\.[^.]*$\", 0, ObjectId)\n| extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n| join kind=leftouter (\n EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\" and (Operation == \"FileDownloaded\" or Operation == \"FileAccessed\")\n | where not(ObjectId endswith \".lnk\") \n and not(ObjectId endswith \".log\") \n and not(ObjectId endswith \".option\") \n and not(ObjectId endswith \".config\") \n and not(ObjectId endswith \".manifest\") \n and not(ObjectId endswith \".partial\")\n) on ObjectId\n| where UserId1 !in (excluded_users)\n| extend userBag = bag_pack(\"UserId\", UserId1, \"ClientIp\", ClientIp1)\n| summarize make_set(UserId1, 10000), userBag = make_bag(userBag), UploadTime = max(TimeGenerated) by UserId, ObjectId, SourceFileName, Extension\n| extend NumberOfUsers = array_length(bag_keys(userBag))\n| project UploadTime, Uploader = UserId, FileLocation = ObjectId, FileName = SourceFileName, AccessedBy = userBag, Extension, NumberOfUsers\n| extend UploaderName = tostring(split(Uploader, \"@\")[0]), UploaderUPNSuffix = tostring(split(Uploader, \"@\")[1])\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "Provides a summary of executable files with double file extensions in SharePoint \n and the users and IP addresses that have accessed them." - }, - { - "name": "tactics", - "value": "DefenseEvasion" - }, - { - "name": "techniques", - "value": "T1036" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject14')._huntingQuerycontentId14),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 14", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject14')._huntingQuerycontentId14)]", - "contentId": "[variables('huntingQueryObject14')._huntingQuerycontentId14]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject14').huntingQueryVersion14]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject14')._huntingQuerycontentId14]", - "contentKind": "HuntingQuery", - "displayName": "Exes with double file extension and access summary", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject14')._huntingQuerycontentId14,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject14')._huntingQuerycontentId14,'-', '2.0.1')))]", - "version": "2.0.1" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject15').huntingQueryTemplateSpecName15]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "new_adminaccountactivity_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject15').huntingQueryVersion15]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_15", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "New Admin Account Activity Seen Which Was Not Seen Historically", - "category": "Hunting Queries", - "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = starttime - 14d;\nlet historicalActivity =\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (lookback .. starttime)\n | where RecordType == \"ExchangeAdmin\" and UserType in (\"Admin\", \"DcAdmin\")\n | summarize historicalCount = count() by UserId;\nlet recentActivity = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where UserType in (\"Admin\", \"DcAdmin\")\n | summarize recentCount = count() by UserId;\nrecentActivity\n| join kind=leftanti (historicalActivity) on UserId\n| project UserId, recentCount\n| join kind=rightsemi (\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where RecordType == \"ExchangeAdmin\" \n | where UserType in (\"Admin\", \"DcAdmin\")\n ) on UserId\n| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by RecordType, Operation, UserType, UserId, ResultStatus\n| extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), UserId)\n| extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '')\n| extend AccountName = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[1]), AccountName)\n| extend AccountNTDomain = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[0]), '')\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "This will help you discover any new admin account activity which was seen and were not seen historically.\nAny new accounts seen in the results can be validated and investigated for any suspicious activities." - }, - { - "name": "tactics", - "value": "PrivilegeEscalation,Collection" - }, - { - "name": "techniques", - "value": "T1078,T1114" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject15')._huntingQuerycontentId15),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 15", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject15')._huntingQuerycontentId15)]", - "contentId": "[variables('huntingQueryObject15')._huntingQuerycontentId15]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject15').huntingQueryVersion15]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject15')._huntingQuerycontentId15]", - "contentKind": "HuntingQuery", - "displayName": "New Admin Account Activity Seen Which Was Not Seen Historically", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject15')._huntingQuerycontentId15,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject15')._huntingQuerycontentId15,'-', '2.0.1')))]", - "version": "2.0.1" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject16').huntingQueryTemplateSpecName16]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "new_sharepoint_downloads_by_IP_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject16').huntingQueryVersion16]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_16", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "GSA Enriched Office 365 - SharePointFileOperation via previously unseen IPs", - "category": "Hunting Queries", - "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = starttime - 14d;\nlet BLOCK_THRESHOLD = 1.0;\n// Identify Autonomous System Numbers (ASNs) with a high block rate in Sign-in Logs\nlet HighBlockRateASNs = SigninLogs\n | where TimeGenerated > lookback\n | where isnotempty(AutonomousSystemNumber)\n | summarize make_set(IPAddress), TotalIps = dcount(IPAddress), BlockedSignins = countif(ResultType == \"50053\"), TotalSignins = count() by AutonomousSystemNumber\n | extend BlockRatio = 1.00 * BlockedSignins / TotalSignins\n | where BlockRatio >= BLOCK_THRESHOLD\n | distinct AutonomousSystemNumber;\n// Retrieve IP addresses from these high block rate ASNs\nlet ASNIPs = SigninLogs\n | where TimeGenerated > lookback\n | where AutonomousSystemNumber in (HighBlockRateASNs)\n | distinct IPAddress, AutonomousSystemNumber;\n// OfficeActivity Query: File activities from identified ASN IPs\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated between(starttime .. endtime)\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | where ClientIP in (ASNIPs)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by ClientIP\n | extend IP_0_Address = ClientIP;\n// EnrichedMicrosoft365AuditLogs Query: File activities from identified ASN IPs\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | where ClientIp in (ASNIPs)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by ClientIp\n | extend IP_0_Address = ClientIp;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by ClientIP;\n// Final Output\nCombinedEvents\n | project StartTime, EndTime, RecentFileActivities, IP_0_Address\n | order by StartTime desc\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "Shows SharePoint upload/download volume by IPs with high-risk ASNs. New IPs with volume spikes may be unauthorized and exfiltrating documents." - }, - { - "name": "tactics", - "value": "Exfiltration" - }, - { - "name": "techniques", - "value": "T1030" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject16')._huntingQuerycontentId16),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 16", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject16')._huntingQuerycontentId16)]", - "contentId": "[variables('huntingQueryObject16')._huntingQuerycontentId16]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject16').huntingQueryVersion16]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject16')._huntingQuerycontentId16]", - "contentKind": "HuntingQuery", - "displayName": "GSA Enriched Office 365 - SharePointFileOperation via previously unseen IPs", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject16')._huntingQuerycontentId16,'-', '2.0.2')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject16')._huntingQuerycontentId16,'-', '2.0.2')))]", - "version": "2.0.2" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject17').huntingQueryTemplateSpecName17]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "new_sharepoint_downloads_by_UserAgent_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject17').huntingQueryVersion17]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_17", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "GSA Enriched Office 365 -SharePointFileOperation via devices with previously unseen user agents", - "category": "Hunting Queries", - "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = starttime - 14d;\nlet MINIMUM_BLOCKS = 10;\nlet SUCCESS_THRESHOLD = 0.2;\n// Identify user agents or client apps with a low success-to-block ratio in Sign-in Logs\nlet HistoricalActivity = SigninLogs\n | where TimeGenerated > lookback\n | where isnotempty(UserAgent)\n | summarize SuccessfulSignins = countif(ResultType == \"0\"), BlockedSignins = countif(ResultType == \"50053\") by UserAgent\n | extend SuccessBlockRatio = 1.00 * SuccessfulSignins / BlockedSignins\n | where SuccessBlockRatio < SUCCESS_THRESHOLD\n | where BlockedSignins > MINIMUM_BLOCKS;\n// OfficeActivity Query: File operations by matching user agents\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated between (starttime .. endtime)\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by UserAgent, UserId, ClientIP, Site_Url\n | join kind=innerunique (HistoricalActivity) on UserAgent\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIP, Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, URL_0_Url = Site_Url;\n// EnrichedMicrosoft365AuditLogs Query: File operations by matching client apps (UserAgent)\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) // Ensure matching with UserAgent column\n | extend SiteUrl = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by UserAgent, UserId, ClientIp, SiteUrl\n | join kind=innerunique (HistoricalActivity) on UserAgent\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIp, Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, URL_0_Url = SiteUrl;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by UserId, ClientIP;\n// Final Output\nCombinedEvents\n | project StartTime, EndTime, RecentFileActivities, IP_0_Address, Account_0_Name, Account_0_UPNSuffix, URL_0_Url\n | order by StartTime desc;\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "Tracking via user agent is one way to differentiate between types of connecting device.\nIn homogeneous enterprise environments the user agent associated with an attacker device may stand out as unusual." - }, - { - "name": "tactics", - "value": "Exfiltration" - }, - { - "name": "techniques", - "value": "T1030" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject17')._huntingQuerycontentId17),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 17", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject17')._huntingQuerycontentId17)]", - "contentId": "[variables('huntingQueryObject17')._huntingQuerycontentId17]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject17').huntingQueryVersion17]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject17')._huntingQuerycontentId17]", - "contentKind": "HuntingQuery", - "displayName": "GSA Enriched Office 365 -SharePointFileOperation via devices with previously unseen user agents", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject17')._huntingQuerycontentId17,'-', '2.0.2')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject17')._huntingQuerycontentId17,'-', '2.0.2')))]", - "version": "2.0.2" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject18').huntingQueryTemplateSpecName18]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "nonowner_MailboxLogin_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject18').huntingQueryVersion18]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_18", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "GSA Enriched Office 365 - Non-owner mailbox login activity", - "category": "Hunting Queries", - "query": "let starttime = todatetime('{{StartTimeISO}}');\n let endtime = todatetime('{{EndTimeISO}}');\n // Enriched Logs Query for Mailbox Logins (non-owner)\n let EnrichedMailboxLogins = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where Workload == \"Exchange\"\n | where Operation == \"MailboxLogin\"\n | extend Logon_Type = tostring(parse_json(tostring(AdditionalProperties)).LogonType)\n | extend MailboxOwnerUPN = tostring(parse_json(tostring(AdditionalProperties)).MailboxOwnerUPN)\n | where Logon_Type != \"Owner\"\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by Operation, UserType, UserId, MailboxOwnerUPN, Logon_Type, ClientIp\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIp\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n // Office Activity Query for Mailbox Logins (non-owner)\n let OfficeMailboxLogins = OfficeActivity\n | where TimeGenerated between (starttime .. endtime)\n | where OfficeWorkload == \"Exchange\"\n | where Operation == \"MailboxLogin\" and Logon_Type != \"Owner\"\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by Operation, OrganizationName, UserType, UserId, MailboxOwnerUPN, Logon_Type, ClientIP\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIP\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n // Combine both results\n let CombinedMailboxLogins = EnrichedMailboxLogins\n | union OfficeMailboxLogins\n | summarize arg_min(StartTime, *) by UserId, MailboxOwnerUPN, Logon_Type;\n // Final output\n CombinedMailboxLogins\n | project StartTime, EndTime, Operation, UserId, MailboxOwnerUPN, Logon_Type, Account_0_Name, Account_0_UPNSuffix, IP_0_Address\n | order by StartTime desc\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "Finds non-owner mailbox access by admin/delegate permissions. Whitelist valid users and check others for unauthorized access." - }, - { - "name": "tactics", - "value": "Collection,Exfiltration" - }, - { - "name": "techniques", - "value": "T1114,T1020" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject18')._huntingQuerycontentId18),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 18", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject18')._huntingQuerycontentId18)]", - "contentId": "[variables('huntingQueryObject18')._huntingQuerycontentId18]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject18').huntingQueryVersion18]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject18')._huntingQuerycontentId18]", - "contentKind": "HuntingQuery", - "displayName": "GSA Enriched Office 365 - Non-owner mailbox login activity", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject18')._huntingQuerycontentId18,'-', '2.0.1')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject18')._huntingQuerycontentId18,'-', '2.0.1')))]", - "version": "2.0.1" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject19').huntingQueryTemplateSpecName19]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "powershell_or_nonbrowser_MailboxLogin_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject19').huntingQueryVersion19]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_19", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "GSA Enriched Office 365 - PowerShell or non-browser mailbox login activity", - "category": "Hunting Queries", - "query": "let starttime = todatetime('{{StartTimeISO}}');\n let endtime = todatetime('{{EndTimeISO}}');\n // EnrichedMicrosoft365AuditLogs query\n let EnrichedMailboxLogin = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where Workload == \"Exchange\" and Operation == \"MailboxLogin\"\n | extend ClientApplication = tostring(parse_json(AdditionalProperties).ClientInfoString)\n | where ClientApplication == \"Client=Microsoft.Exchange.Powershell; Microsoft WinRM Client\"\n | extend TenantName = tostring(parse_json(AdditionalProperties).TenantName)\n | extend MailboxOwner = tostring(parse_json(AdditionalProperties).MailboxOwnerUPN)\n | extend LogonType = tostring(parse_json(AdditionalProperties).LogonType)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Count = count() by Operation, TenantName, UserType, UserId, MailboxOwner, LogonType, ClientApplication\n | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), UserId)\n | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '')\n | extend AccountName = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[1]), AccountName)\n | extend AccountNTDomain = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[0]), '');\n // OfficeActivity query\n let OfficeMailboxLogin = OfficeActivity\n | where TimeGenerated between (starttime .. endtime)\n | where OfficeWorkload == \"Exchange\" and Operation == \"MailboxLogin\"\n | where ClientInfoString == \"Client=Microsoft.Exchange.Powershell; Microsoft WinRM Client\"\n | extend LogonType = \"Unknown\" // If LogonType does not exist, create a placeholder\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Count = count() by Operation, OrganizationName, UserType, UserId, MailboxOwnerUPN, LogonType, ClientInfoString\n | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), UserId)\n | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '')\n | extend AccountName = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[1]), AccountName)\n | extend AccountNTDomain = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[0]), '');\n // Combine Enriched and Office queries\n let CombinedMailboxLogin = EnrichedMailboxLogin\n | union OfficeMailboxLogin\n | summarize arg_min(StartTime, *) by UserId, Operation\n | project StartTime, EndTime, Operation, TenantName, OrganizationName, UserType, UserId, MailboxOwner, LogonType, ClientApplication, ClientInfoString, Count, AccountName, AccountUPNSuffix, AccountNTDomain;\n // Final output\n CombinedMailboxLogin\n | order by StartTime desc;\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "Detects mailbox login from Exchange PowerShell. All accounts can use it by default, but admins can change it. Whitelist benign activities." - }, - { - "name": "tactics", - "value": "Execution,Persistence,Collection" - }, - { - "name": "techniques", - "value": "T1059,T1098,T1114" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject19')._huntingQuerycontentId19),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 19", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject19')._huntingQuerycontentId19)]", - "contentId": "[variables('huntingQueryObject19')._huntingQuerycontentId19]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject19').huntingQueryVersion19]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject19')._huntingQuerycontentId19]", - "contentKind": "HuntingQuery", - "displayName": "GSA Enriched Office 365 - PowerShell or non-browser mailbox login activity", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject19')._huntingQuerycontentId19,'-', '2.0.2')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject19')._huntingQuerycontentId19,'-', '2.0.2')))]", - "version": "2.0.2" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject20').huntingQueryTemplateSpecName20]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "sharepoint_downloads_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject20').huntingQueryVersion20]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_20", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "GSA Enriched Office 365 - SharePoint File Operation via Client IP with Previously Unseen User Agents", - "category": "Hunting Queries", - "query": "let starttime = todatetime('{{StartTimeISO}}');\n let endtime = todatetime('{{EndTimeISO}}');\n let lookback = starttime - 14d;\n // Historical user agents in EnrichedMicrosoft365AuditLogs\n let historicalUA_Enriched = EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | where TimeGenerated between (lookback .. starttime)\n | extend ClientApplication = tostring(parse_json(AdditionalProperties).UserAgent)\n | summarize by ClientIp, ClientApplication;\n // Recent user agents in EnrichedMicrosoft365AuditLogs\n let recentUA_Enriched = EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | where TimeGenerated between (starttime .. endtime)\n | extend ClientApplication = tostring(parse_json(AdditionalProperties).UserAgent)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by ClientIp, ClientApplication;\n // Combine historical and recent user agents from EnrichedMicrosoft365AuditLogs\n let Enriched_UA = recentUA_Enriched\n | join kind=leftanti (historicalUA_Enriched) on ClientIp, ClientApplication\n | where not(isempty(ClientIp))\n | extend IP_0_Address = ClientIp;\n // Historical user agents in OfficeActivity\n let historicalUA_Office = OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | where TimeGenerated between (lookback .. starttime)\n | summarize by ClientIP, UserAgent;\n // Recent user agents in OfficeActivity\n let recentUA_Office = OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | where TimeGenerated between (starttime .. endtime)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by ClientIP, UserAgent;\n // Combine historical and recent user agents from OfficeActivity\n let Office_UA = recentUA_Office\n | join kind=leftanti (historicalUA_Office) on ClientIP, UserAgent\n | where not(isempty(ClientIP))\n | extend IP_0_Address = ClientIP;\n // Final combined result\n Enriched_UA\n | union Office_UA\n | project StartTime, EndTime, ClientIp, ClientApplication, IP_0_Address;\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "New user agents associated with a client IP for SharePoint file uploads/downloads." - }, - { - "name": "tactics", - "value": "Exfiltration" - }, - { - "name": "techniques", - "value": "T1030" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject20')._huntingQuerycontentId20),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 20", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject20')._huntingQuerycontentId20)]", - "contentId": "[variables('huntingQueryObject20')._huntingQuerycontentId20]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject20').huntingQueryVersion20]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject20')._huntingQuerycontentId20]", - "contentKind": "HuntingQuery", - "displayName": "GSA Enriched Office 365 - SharePoint File Operation via Client IP with Previously Unseen User Agents", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject20')._huntingQuerycontentId20,'-', '2.0.2')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject20')._huntingQuerycontentId20,'-', '2.0.2')))]", - "version": "2.0.2" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", - "apiVersion": "2023-04-01-preview", - "name": "[variables('huntingQueryObject21').huntingQueryTemplateSpecName21]", - "location": "[parameters('workspace-location')]", - "dependsOn": [ - "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" - ], - "properties": { - "description": "MultipleUsersEmailForwardedToSameDestination_HuntingQueries Hunting Query with template version 3.1.0", - "mainTemplate": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "[variables('huntingQueryObject21').huntingQueryVersion21]", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.OperationalInsights/savedSearches", - "apiVersion": "2022-10-01", - "name": "Global_Secure_Access_Hunting_Query_21", - "location": "[parameters('workspace-location')]", - "properties": { - "eTag": "*", - "displayName": "GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination", - "category": "Hunting Queries", - "query": "let queryfrequency = 1d;\nlet queryperiod = 7d;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated > ago(queryperiod)\n | where Workload == \"Exchange\"\n | where AdditionalProperties has_any (\"ForwardTo\", \"RedirectTo\", \"ForwardingSmtpAddress\")\n | mv-apply DynamicParameters = todynamic(AdditionalProperties) on (\n summarize ParsedParameters = make_bag(bag_pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source')\n | extend DestinationMailAddress = tolower(case(\n isnotempty(column_ifexists(\"ForwardTo\", \"\")), column_ifexists(\"ForwardTo\", \"\"),\n isnotempty(column_ifexists(\"RedirectTo\", \"\")), column_ifexists(\"RedirectTo\", \"\"),\n isnotempty(column_ifexists(\"ForwardingSmtpAddress\", \"\")), trim_start(@\"smtp:\", column_ifexists(\"ForwardingSmtpAddress\", \"\")),\n \"\"\n ))\n | where isnotempty(DestinationMailAddress)\n | mv-expand split(DestinationMailAddress, \";\")\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIp)[0]\n | extend ClientIp = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1])\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIp\n | where DistinctUserCount > 1 and EndTime > ago(queryfrequency)\n | mv-expand UserId to typeof(string)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated > ago(queryperiod)\n | where OfficeWorkload =~ \"Exchange\"\n | where Parameters has_any (\"ForwardTo\", \"RedirectTo\", \"ForwardingSmtpAddress\")\n | mv-apply DynamicParameters = todynamic(Parameters) on (\n summarize ParsedParameters = make_bag(bag_pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source')\n | extend DestinationMailAddress = tolower(case(\n isnotempty(column_ifexists(\"ForwardTo\", \"\")), column_ifexists(\"ForwardTo\", \"\"),\n isnotempty(column_ifexists(\"RedirectTo\", \"\")), column_ifexists(\"RedirectTo\", \"\"),\n isnotempty(column_ifexists(\"ForwardingSmtpAddress\", \"\")), trim_start(@\"smtp:\", column_ifexists(\"ForwardingSmtpAddress\", \"\")),\n \"\"\n ))\n | where isnotempty(DestinationMailAddress)\n | mv-expand split(DestinationMailAddress, \";\")\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIP)[0]\n | extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1])\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP\n | where DistinctUserCount > 1 and EndTime > ago(queryfrequency)\n | mv-expand UserId to typeof(string)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, IP_0_Address = ClientIP;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by DestinationMailAddress, ClientIp;\n// Final Output\nCombinedEvents\n | project StartTime, EndTime, DestinationMailAddress, ClientIp, Ports, UserId, AccountName, AccountUPNSuffix\n | order by StartTime desc\n", - "version": 2, - "tags": [ - { - "name": "description", - "value": "Identifies when multiple (more than one) users' mailboxes are configured to forward to the same destination. \nThis could be an attacker-controlled destination mailbox configured to collect mail from multiple compromised user accounts." - }, - { - "name": "tactics", - "value": "Collection,Exfiltration" - }, - { - "name": "techniques", - "value": "T1114,T1020" - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", - "apiVersion": "2022-01-01-preview", - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject21')._huntingQuerycontentId21),'/'))))]", - "properties": { - "description": "Global Secure Access Hunting Query 21", - "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject21')._huntingQuerycontentId21)]", - "contentId": "[variables('huntingQueryObject21')._huntingQuerycontentId21]", - "kind": "HuntingQuery", - "version": "[variables('huntingQueryObject21').huntingQueryVersion21]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "tier": "Partner", - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - } - } - } - ] - }, - "packageKind": "Solution", - "packageVersion": "[variables('_solutionVersion')]", - "packageName": "[variables('_solutionName')]", - "packageId": "[variables('_solutionId')]", - "contentSchemaVersion": "3.0.0", - "contentId": "[variables('huntingQueryObject21')._huntingQuerycontentId21]", - "contentKind": "HuntingQuery", - "displayName": "GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination", - "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject21')._huntingQuerycontentId21,'-', '2.0.2')))]", - "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject21')._huntingQuerycontentId21,'-', '2.0.2')))]", - "version": "2.0.2" - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces/providers/contentPackages", - "apiVersion": "2023-04-01-preview", - "location": "[parameters('workspace-location')]", - "properties": { - "version": "3.1.0", - "kind": "Solution", - "contentSchemaVersion": "3.0.0", - "displayName": "Global Secure Access", - "publisherDisplayName": "Microsoft Corporation", - "descriptionHtml": "

Note: Please refer to the following before installing the solution:

\n
\n

ā€¢ There may be known issues pertaining to this Solution, please refer to them before installing.

\n

Global Secure Access is a domain solution and does not include any data connectors. The content in this solution requires one of the product solutions below.

\n

Prerequisite:

\n

Install one or more of the listed solutions to unlock the value provided by this solution.

\n
    \n
  1. Microsoft Entra ID
  2. \n
\n

Underlying Microsoft Technologies used:

\n

This solution depends on the following technologies, and some of these dependencies may either be in Preview state or might result in additional ingestion or operational costs:

\n
    \n
  1. Product solutions as described above
  2. \n
\n

Workbooks: 2, Analytic Rules: 19, Hunting Queries: 21

\n

Learn more about Microsoft Sentinel | Learn more about Solutions

\n", - "contentKind": "Solution", - "contentProductId": "[variables('_solutioncontentProductId')]", - "id": "[variables('_solutioncontentProductId')]", - "icon": "", - "contentId": "[variables('_solutionId')]", - "parentId": "[variables('_solutionId')]", - "source": { - "kind": "Solution", - "name": "Global Secure Access", - "sourceId": "[variables('_solutionId')]" - }, - "author": { - "name": "Microsoft", - "email": "[variables('_email')]" - }, - "support": { - "name": "Microsoft Corporation", - "email": "GSASentinelSupport@microsoft.com", - "tier": "Partner", - "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" - }, - "dependencies": { - "operator": "AND", - "criteria": [ - { - "kind": "Workbook", - "contentId": "[variables('_workbookContentId1')]", - "version": "[variables('workbookVersion1')]" - }, - { - "kind": "Workbook", - "contentId": "[variables('_workbookContentId2')]", - "version": "[variables('workbookVersion2')]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject1')._analyticRulecontentId1]", - "version": "[variables('analyticRuleObject1').analyticRuleVersion1]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject2')._analyticRulecontentId2]", - "version": "[variables('analyticRuleObject2').analyticRuleVersion2]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject3')._analyticRulecontentId3]", - "version": "[variables('analyticRuleObject3').analyticRuleVersion3]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject4')._analyticRulecontentId4]", - "version": "[variables('analyticRuleObject4').analyticRuleVersion4]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject5')._analyticRulecontentId5]", - "version": "[variables('analyticRuleObject5').analyticRuleVersion5]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject6')._analyticRulecontentId6]", - "version": "[variables('analyticRuleObject6').analyticRuleVersion6]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject7')._analyticRulecontentId7]", - "version": "[variables('analyticRuleObject7').analyticRuleVersion7]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject8')._analyticRulecontentId8]", - "version": "[variables('analyticRuleObject8').analyticRuleVersion8]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject9')._analyticRulecontentId9]", - "version": "[variables('analyticRuleObject9').analyticRuleVersion9]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject10')._analyticRulecontentId10]", - "version": "[variables('analyticRuleObject10').analyticRuleVersion10]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject11')._analyticRulecontentId11]", - "version": "[variables('analyticRuleObject11').analyticRuleVersion11]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject12')._analyticRulecontentId12]", - "version": "[variables('analyticRuleObject12').analyticRuleVersion12]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject13')._analyticRulecontentId13]", - "version": "[variables('analyticRuleObject13').analyticRuleVersion13]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject14')._analyticRulecontentId14]", - "version": "[variables('analyticRuleObject14').analyticRuleVersion14]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject15')._analyticRulecontentId15]", - "version": "[variables('analyticRuleObject15').analyticRuleVersion15]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject16')._analyticRulecontentId16]", - "version": "[variables('analyticRuleObject16').analyticRuleVersion16]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject17')._analyticRulecontentId17]", - "version": "[variables('analyticRuleObject17').analyticRuleVersion17]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject18')._analyticRulecontentId18]", - "version": "[variables('analyticRuleObject18').analyticRuleVersion18]" - }, - { - "kind": "AnalyticsRule", - "contentId": "[variables('analyticRuleObject19')._analyticRulecontentId19]", - "version": "[variables('analyticRuleObject19').analyticRuleVersion19]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject1')._huntingQuerycontentId1]", - "version": "[variables('huntingQueryObject1').huntingQueryVersion1]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject2')._huntingQuerycontentId2]", - "version": "[variables('huntingQueryObject2').huntingQueryVersion2]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject3')._huntingQuerycontentId3]", - "version": "[variables('huntingQueryObject3').huntingQueryVersion3]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject4')._huntingQuerycontentId4]", - "version": "[variables('huntingQueryObject4').huntingQueryVersion4]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject5')._huntingQuerycontentId5]", - "version": "[variables('huntingQueryObject5').huntingQueryVersion5]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject6')._huntingQuerycontentId6]", - "version": "[variables('huntingQueryObject6').huntingQueryVersion6]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject7')._huntingQuerycontentId7]", - "version": "[variables('huntingQueryObject7').huntingQueryVersion7]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject8')._huntingQuerycontentId8]", - "version": "[variables('huntingQueryObject8').huntingQueryVersion8]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject9')._huntingQuerycontentId9]", - "version": "[variables('huntingQueryObject9').huntingQueryVersion9]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject10')._huntingQuerycontentId10]", - "version": "[variables('huntingQueryObject10').huntingQueryVersion10]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject11')._huntingQuerycontentId11]", - "version": "[variables('huntingQueryObject11').huntingQueryVersion11]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject12')._huntingQuerycontentId12]", - "version": "[variables('huntingQueryObject12').huntingQueryVersion12]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject13')._huntingQuerycontentId13]", - "version": "[variables('huntingQueryObject13').huntingQueryVersion13]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject14')._huntingQuerycontentId14]", - "version": "[variables('huntingQueryObject14').huntingQueryVersion14]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject15')._huntingQuerycontentId15]", - "version": "[variables('huntingQueryObject15').huntingQueryVersion15]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject16')._huntingQuerycontentId16]", - "version": "[variables('huntingQueryObject16').huntingQueryVersion16]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject17')._huntingQuerycontentId17]", - "version": "[variables('huntingQueryObject17').huntingQueryVersion17]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject18')._huntingQuerycontentId18]", - "version": "[variables('huntingQueryObject18').huntingQueryVersion18]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject19')._huntingQuerycontentId19]", - "version": "[variables('huntingQueryObject19').huntingQueryVersion19]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject20')._huntingQuerycontentId20]", - "version": "[variables('huntingQueryObject20').huntingQueryVersion20]" - }, - { - "kind": "HuntingQuery", - "contentId": "[variables('huntingQueryObject21')._huntingQuerycontentId21]", - "version": "[variables('huntingQueryObject21').huntingQueryVersion21]" - } - ] - }, - "firstPublishDate": "2024-04-08", - "providers": [ - "Microsoft" - ], - "categories": { - "domains": [ - "Identity" - ] - } - }, - "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/', variables('_solutionId'))]" - } - ], - "outputs": {} -} diff --git a/Solutions/Global Secure Access/Package/testParameters.json b/Solutions/Global Secure Access/Package/testParameters.json deleted file mode 100644 index 8dd674f595..0000000000 --- a/Solutions/Global Secure Access/Package/testParameters.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "location": { - "type": "string", - "minLength": 1, - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Not used, but needed to pass arm-ttk test `Location-Should-Not-Be-Hardcoded`. We instead use the `workspace-location` which is derived from the LA workspace" - } - }, - "workspace-location": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "[concat('Region to deploy solution resources -- separate from location selection',parameters('location'))]" - } - }, - "workspace": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Workspace name for Log Analytics where Microsoft Sentinel is setup" - } - }, - "workbook1-name": { - "type": "string", - "defaultValue": "Microsoft Global Secure Access Enriched M365 Logs", - "minLength": 1, - "metadata": { - "description": "Name for the workbook" - } - }, - "workbook2-name": { - "type": "string", - "defaultValue": "Microsoft Global Secure Access Traffic Logs", - "minLength": 1, - "metadata": { - "description": "Name for the workbook" - } - } -} diff --git a/Solutions/Microsoft 365/Analytic Rules/sharepoint_file_transfer_above_threshold.yaml b/Solutions/Microsoft 365/Analytic Rules/sharepoint_file_transfer_above_threshold.yaml index c980984145..25267f8d2b 100644 --- a/Solutions/Microsoft 365/Analytic Rules/sharepoint_file_transfer_above_threshold.yaml +++ b/Solutions/Microsoft 365/Analytic Rules/sharepoint_file_transfer_above_threshold.yaml @@ -55,5 +55,5 @@ incidentConfiguration: - Account groupByAlertDetails: [] groupByCustomDetails: [] -version: 1.0.5 +version: 1.0.6 kind: Scheduled From 1c2fbd680bf112d028aa77cb7344487f30f8f918 Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Sun, 6 Oct 2024 15:46:37 +0300 Subject: [PATCH 19/27] package --- .../Data/Solution_GlobalSecureAccess.json | 27 +- .../Global Secure Access/Package/3.0.0.zip | Bin 0 -> 48410 bytes .../Package/createUiDefinition.json | 729 +++ .../Package/mainTemplate.json | 5024 +++++++++++++++++ .../Package/testParameters.json | 40 + 5 files changed, 5806 insertions(+), 14 deletions(-) create mode 100644 Solutions/Global Secure Access/Package/3.0.0.zip create mode 100644 Solutions/Global Secure Access/Package/createUiDefinition.json create mode 100644 Solutions/Global Secure Access/Package/mainTemplate.json create mode 100644 Solutions/Global Secure Access/Package/testParameters.json diff --git a/Solutions/Global Secure Access/Data/Solution_GlobalSecureAccess.json b/Solutions/Global Secure Access/Data/Solution_GlobalSecureAccess.json index 990dd54b3c..d1978bff0b 100644 --- a/Solutions/Global Secure Access/Data/Solution_GlobalSecureAccess.json +++ b/Solutions/Global Secure Access/Data/Solution_GlobalSecureAccess.json @@ -6,11 +6,11 @@ "WorkbookBladeDescription": "This Microsoft Sentinel Solution installs workbooks. Workbooks provide a flexible canvas for data monitoring, analysis, and the creation of rich visual reports within the Azure portal. They allow you to tap into one or many data sources from Microsoft Sentinel and combine them into unified interactive experiences.", "AnalyticalRuleBladeDescription": "This solution installs the following analytic rule templates. After installing the solution, create and enable analytic rules in the Manage solution view.", "HuntingQueryBladeDescription": "This solution installs the following hunting queries. After installing the solution, run these hunting queries to hunt for threats in the Manage solution view.", - "Workbooks": [ + "Workbooks": [ "Workbooks/GSAM365EnrichedEvents.json", "Workbooks/GSANetworkTraffic.json" ], - "Analytic Rules": [ + "Analytic Rules": [ "Analytic Rules/Identity - AfterHoursActivity.yaml", "Analytic Rules/Identity - SharedSessions.yaml", "Analytic Rules/Office 365 - exchange_auditlogdisabled.yaml", @@ -30,33 +30,32 @@ "Analytic Rules/SWG - Abnormal Deny Rate.yaml", "Analytic Rules/SWG - Abnormal Port to Protocol.yaml", "Analytic Rules/SWG - Source IP Port Scan.yaml" - ], - "Playbooks": [], + ], "Hunting Queries": [ "Hunting Queries/AnomolousUserAccessingOtherUsersMailbox.yaml", + "Hunting Queries/double_file_ext_exes.yaml", "Hunting Queries/ExternalUserAddedRemovedInTeams_HuntVersion.yaml", "Hunting Queries/ExternalUserFromNewOrgAddedToTeams.yaml", "Hunting Queries/Mail_redirect_via_ExO_transport_rule_hunting.yaml", + "Hunting Queries/MultipleTeamsDeletes.yaml", + "Hunting Queries/MultipleUsersEmailForwardedToSameDestination.yaml", "Hunting Queries/MultiTeamBot.yaml", "Hunting Queries/MultiTeamOwner.yaml", - "Hunting Queries/MultipleTeamsDeletes.yaml", "Hunting Queries/NewBotAddedToTeams.yaml", - "Hunting Queries/New_WindowsReservedFileNamesOnOfficeFileServices.yaml", - "Hunting Queries/OfficeMailForwarding_hunting.yaml", - "Hunting Queries/TeamsFilesUploaded.yaml", - "Hunting Queries/UserAddToTeamsAndUploadsFile.yaml", - "Hunting Queries/WindowsReservedFileNamesOnOfficeFileServices.yaml", - "Hunting Queries/double_file_ext_exes.yaml", "Hunting Queries/new_adminaccountactivity.yaml", "Hunting Queries/new_sharepoint_downloads_by_IP.yaml", "Hunting Queries/new_sharepoint_downloads_by_UserAgent.yaml", + "Hunting Queries/New_WindowsReservedFileNamesOnOfficeFileServices.yaml", "Hunting Queries/nonowner_MailboxLogin.yaml", + "Hunting Queries/OfficeMailForwarding_hunting.yaml", "Hunting Queries/powershell_or_nonbrowser_MailboxLogin.yaml", "Hunting Queries/sharepoint_downloads.yaml", - "Hunting Queries/MultipleUsersEmailForwardedToSameDestination.yaml" - ], + "Hunting Queries/TeamsFilesUploaded.yaml", + "Hunting Queries/UserAddToTeamsAndUploadsFile.yaml", + "Hunting Queries/WindowsReservedFileNamesOnOfficeFileServices.yaml" + ], "BasePath": "C:\\git\\Azure-Sentinel\\Azure-Sentinel\\Solutions\\Global Secure Access", - "Version": "3.1.0", + "Version": "3.0.1", "Metadata": "SolutionMetadata.json", "TemplateSpec": true, "StaticDataConnectorIds": [ diff --git a/Solutions/Global Secure Access/Package/3.0.0.zip b/Solutions/Global Secure Access/Package/3.0.0.zip new file mode 100644 index 0000000000000000000000000000000000000000..cf955a94a0e2dc93e90d48ef06baa7636fbb7f80 GIT binary patch literal 48410 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\n\n**Note:** Please refer to the following before installing the solution: \n\nā€¢ Review the solution [Release Notes](https://github.com/Azure/Azure-Sentinel/tree/master/Solutions/Global%20Secure%20Access/ReleaseNotes.md)\n\n ā€¢ There may be [known issues](https://aka.ms/sentinelsolutionsknownissues) pertaining to this Solution, please refer to them before installing.\n\n[Global Secure Access](https://aka.ms/GlobalSecureAccess) is a [domain solution](https://learn.microsoft.com/en-us/azure/sentinel/sentinel-solutions-catalog#domain-solutions) and does not include any data connectors. The content in this solution requires one of the product solutions below.\n\n**Prerequisite:**\n\nInstall one or more of the listed solutions to unlock the value provided by this solution.\n1. Microsoft Entra ID \n\n**Underlying Microsoft Technologies used:**\n\nThis solution depends on the following technologies, and some of these dependencies may either be in Preview state or might result in additional ingestion or operational costs:\n1. Product solutions as described above\n\n\n**Workbooks:** 2, **Analytic Rules:** 19, **Hunting Queries:** 21\n\n[Learn more about Microsoft Sentinel](https://aka.ms/azuresentinel) | [Learn more about Solutions](https://aka.ms/azuresentinelsolutionsdoc)", + "subscription": { + "resourceProviders": [ + "Microsoft.OperationsManagement/solutions", + "Microsoft.OperationalInsights/workspaces/providers/alertRules", + "Microsoft.Insights/workbooks", + "Microsoft.Logic/workflows" + ] + }, + "location": { + "metadata": { + "hidden": "Hiding location, we get it from the log analytics workspace" + }, + "visible": false + }, + "resourceGroup": { + "allowExisting": true + } + } + }, + "basics": [ + { + "name": "getLAWorkspace", + "type": "Microsoft.Solutions.ArmApiControl", + "toolTip": "This filters by workspaces that exist in the Resource Group selected", + "condition": "[greater(length(resourceGroup().name),0)]", + "request": { + "method": "GET", + "path": "[concat(subscription().id,'/providers/Microsoft.OperationalInsights/workspaces?api-version=2020-08-01')]" + } + }, + { + "name": "workspace", + "type": "Microsoft.Common.DropDown", + "label": "Workspace", + "placeholder": "Select a workspace", + "toolTip": "This dropdown will list only workspace that exists in the Resource Group selected", + "constraints": { + "allowedValues": "[map(filter(basics('getLAWorkspace').value, (filter) => contains(toLower(filter.id), toLower(resourceGroup().name))), (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]", + "required": true + }, + "visible": true + } + ], + "steps": [ + { + "name": "workbooks", + "label": "Workbooks", + "subLabel": { + "preValidation": "Configure the workbooks", + "postValidation": "Done" + }, + "bladeTitle": "Workbooks", + "elements": [ + { + "name": "workbooks-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This Microsoft Sentinel Solution installs workbooks. Workbooks provide a flexible canvas for data monitoring, analysis, and the creation of rich visual reports within the Azure portal. They allow you to tap into one or many data sources from Microsoft Sentinel and combine them into unified interactive experiences." + } + }, + { + "name": "workbooks-link", + "type": "Microsoft.Common.TextBlock", + "options": { + "link": { + "label": "Learn more", + "uri": "https://docs.microsoft.com/azure/sentinel/tutorial-monitor-your-data" + } + } + }, + { + "name": "workbook1", + "type": "Microsoft.Common.Section", + "label": "Microsoft Global Secure Access Enriched M365 Logs", + "elements": [ + { + "name": "workbook1-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This Workbook provides a detailed view of Microsoft 365 log data, enriched with contextual information to enhance visibility into user activities and potential security threats." + } + } + ] + }, + { + "name": "workbook2", + "type": "Microsoft.Common.Section", + "label": "Microsoft Global Secure Access Traffic Logs", + "elements": [ + { + "name": "workbook2-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This workbook provides an overview of all traffic logs within your network, offering insights into data transfer, anomalies, and potential threats." + } + } + ] + } + ] + }, + { + "name": "analytics", + "label": "Analytics", + "subLabel": { + "preValidation": "Configure the analytics", + "postValidation": "Done" + }, + "bladeTitle": "Analytics", + "elements": [ + { + "name": "analytics-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This solution installs the following analytic rule templates. After installing the solution, create and enable analytic rules in the Manage solution view." + } + }, + { + "name": "analytics-link", + "type": "Microsoft.Common.TextBlock", + "options": { + "link": { + "label": "Learn more", + "uri": "https://docs.microsoft.com/azure/sentinel/tutorial-detect-threats-custom?WT.mc_id=Portal-Microsoft_Azure_CreateUIDef" + } + } + }, + { + "name": "analytic1", + "type": "Microsoft.Common.Section", + "label": "Detect Connections Outside Operational Hours", + "elements": [ + { + "name": "analytic1-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This query identifies connections that occur outside of the defined operational hours. It helps in monitoring and flagging any unusual activity that may occur during non-business hours, indicating potential security concerns or policy violations." + } + } + ] + }, + { + "name": "analytic2", + "type": "Microsoft.Common.Section", + "label": "Detect IP Address Changes and Overlapping Sessions", + "elements": [ + { + "name": "analytic2-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This query identifies network sessions based on DeviceId and UserPrincipalName, then checks for changed IP addresses and overlapping session times." + } + } + ] + }, + { + "name": "analytic3", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Exchange AuditLog Disabled", + "elements": [ + { + "name": "analytic3-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Identifies when the Exchange audit logging has been disabled, which may indicate an adversary attempt to evade detection or bypass other defenses." + } + } + ] + }, + { + "name": "analytic4", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Accessed files shared by temporary external user", + "elements": [ + { + "name": "analytic4-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This detection identifies when an external user is added to a Team or Teams chat and shares a file which is accessed by many users (>10) and the users is removed within short period of time. This might be an indicator of suspicious activity." + } + } + ] + }, + { + "name": "analytic5", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - External User Added and Removed in Short Timeframe", + "elements": [ + { + "name": "analytic5-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This detection flags the occurrences of external user accounts that are added to a Team and then removed within one hour." + } + } + ] + }, + { + "name": "analytic6", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule", + "elements": [ + { + "name": "analytic6-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Identifies when an Exchange Online transport rule is configured to forward emails.\nThis could indicate an adversary mailbox configured to collect mail from multiple user accounts." + } + } + ] + }, + { + "name": "analytic7", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Malicious Inbox Rule", + "elements": [ + { + "name": "analytic7-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Often times after the initial compromise the attackers create inbox rules to delete emails that contain certain keywords.\nThis is done so as to limit ability to warn compromised users that they've been compromised. Below is a sample query that tries to detect this.\nReference: https://www.reddit.com/r/sysadmin/comments/7kyp0a/recent_phishing_attempts_my_experience_and_what/" + } + } + ] + }, + { + "name": "analytic8", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Multiple Teams deleted by a single user", + "elements": [ + { + "name": "analytic8-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This detection flags the occurrences of deleting multiple teams within a day.\nThis data is a part of Office 365 Connector in Microsoft Sentinel." + } + } + ] + }, + { + "name": "analytic9", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination", + "elements": [ + { + "name": "analytic9-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Identifies when multiple (more than one) users' mailboxes are configured to forward to the same destination. \nThis could be an attacker-controlled destination mailbox configured to collect mail from multiple compromised user accounts." + } + } + ] + }, + { + "name": "analytic10", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Office Policy Tampering", + "elements": [ + { + "name": "analytic10-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Identifies if any tampering is done to either audit log, ATP Safelink, SafeAttachment, AntiPhish, or Dlp policy. \nAn adversary may use this technique to evade detection or avoid other policy-based defenses.\nReferences: https://docs.microsoft.com/powershell/module/exchange/advanced-threat-protection/remove-antiphishrule?view=exchange-ps." + } + } + ] + }, + { + "name": "analytic11", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - New Executable via Office FileUploaded Operation", + "elements": [ + { + "name": "analytic11-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Identifies when executable file types are uploaded to Office services such as SharePoint and OneDrive.\nList currently includes exe, inf, gzip, cmd, bat file extensions.\nAdditionally, identifies when a given user is uploading these files to another user's workspace.\nThis may be an indication of a staging location for malware or other malicious activity." + } + } + ] + }, + { + "name": "analytic12", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Rare and Potentially High-Risk Office Operations", + "elements": [ + { + "name": "analytic12-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Identifies Office operations that are typically rare and can provide capabilities useful to attackers." + } + } + ] + }, + { + "name": "analytic13", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - SharePoint File Operation via Previously Unseen IPs", + "elements": [ + { + "name": "analytic13-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Identifies anomalies using user behavior by setting a threshold for significant changes in file upload/download activities from new IP addresses. It establishes a baseline of typical behavior, compares it to recent activity, and flags deviations exceeding a default threshold of 25." + } + } + ] + }, + { + "name": "analytic14", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - SharePointFileOperation via devices with previously unseen user agents", + "elements": [ + { + "name": "analytic14-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Identifies anomalies if the number of documents uploaded or downloaded from device(s) associated with a previously unseen user agent exceeds a threshold (default is 5) and deviation (default is 25%)." + } + } + ] + }, + { + "name": "analytic15", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold", + "elements": [ + { + "name": "analytic15-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Identifies Office365 SharePoint file transfers above a certain threshold in a 15-minute time period.\nPlease note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur." + } + } + ] + }, + { + "name": "analytic16", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold", + "elements": [ + { + "name": "analytic16-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Identifies Office365 SharePoint file transfers with a distinct folder count above a certain threshold in a 15-minute time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur." + } + } + ] + }, + { + "name": "analytic17", + "type": "Microsoft.Common.Section", + "label": "Detect Abnormal Deny Rate for Source to Destination IP", + "elements": [ + { + "name": "analytic17-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Identifies abnormal deny rate for specific source IP to destination IP based on the normal average and standard deviation learned during a configured period. This can indicate potential exfiltration, initial access, or C2, where an attacker tries to exploit the same vulnerability on machines in the organization but is being blocked by firewall rules." + } + } + ] + }, + { + "name": "analytic18", + "type": "Microsoft.Common.Section", + "label": "Detect Protocol Changes for Destination Ports", + "elements": [ + { + "name": "analytic18-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Identifies changes in the protocol used for specific destination ports, comparing the current runtime with a learned baseline. This can indicate potential protocol misuse or configuration changes." + } + } + ] + }, + { + "name": "analytic19", + "type": "Microsoft.Common.Section", + "label": "Detect Source IP Scanning Multiple Open Ports", + "elements": [ + { + "name": "analytic19-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Identifies a source IP scanning multiple open ports on Global Secure Access Firewall. This can indicate malicious scanning of ports by an attacker, trying to reveal open ports in the organization that can be compromised for initial access." + } + } + ] + } + ] + }, + { + "name": "huntingqueries", + "label": "Hunting Queries", + "bladeTitle": "Hunting Queries", + "elements": [ + { + "name": "huntingqueries-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This solution installs the following hunting queries. After installing the solution, run these hunting queries to hunt for threats in the Manage solution view." + } + }, + { + "name": "huntingqueries-link", + "type": "Microsoft.Common.TextBlock", + "options": { + "link": { + "label": "Learn more", + "uri": "https://docs.microsoft.com/azure/sentinel/hunting" + } + } + }, + { + "name": "huntingquery1", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Anomalous access to other users' mailboxes", + "elements": [ + { + "name": "huntingquery1-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Looks for users accessing multiple other users' mailboxes or accessing multiple folders in another users mailbox. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery2", + "type": "Microsoft.Common.Section", + "label": "Exes with double file extension and access summary", + "elements": [ + { + "name": "huntingquery2-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Provides a summary of executable files with double file extensions in SharePoint \n and the users and IP addresses that have accessed them. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery3", + "type": "Microsoft.Common.Section", + "label": "External User Added and Removed in a Short Timeframe", + "elements": [ + { + "name": "huntingquery3-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This hunting query identifies external user accounts that are added to a Team and then removed within one hour. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery4", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - External user from a new organisation added to Teams", + "elements": [ + { + "name": "huntingquery4-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This query identifies external users added to Teams where the user's domain is not one previously seen in Teams data. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery5", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule", + "elements": [ + { + "name": "huntingquery5-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Identifies when Exchange Online transport rule is configured to forward emails.\nThis could be an adversary mailbox configured to collect mail from multiple user accounts. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery6", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Multiple Teams deleted by a single user", + "elements": [ + { + "name": "huntingquery6-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This hunting query identifies where multiple Teams have been deleted by a single user in a short timeframe. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery7", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination", + "elements": [ + { + "name": "huntingquery7-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Identifies when multiple (more than one) users' mailboxes are configured to forward to the same destination. \nThis could be an attacker-controlled destination mailbox configured to collect mail from multiple compromised user accounts. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery8", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Bots added to multiple teams", + "elements": [ + { + "name": "huntingquery8-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This hunting query helps identify bots added to multiple Teams in a short space of time. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery9", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - User made Owner of multiple teams", + "elements": [ + { + "name": "huntingquery9-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This hunting query identifies users who have been made Owner of multiple Teams. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery10", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Previously Unseen Bot or Application Added to Teams", + "elements": [ + { + "name": "huntingquery10-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This hunting query helps identify new, and potentially unapproved applications or bots being added to Teams. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery11", + "type": "Microsoft.Common.Section", + "label": "New Admin Account Activity Seen Which Was Not Seen Historically", + "elements": [ + { + "name": "huntingquery11-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This will help you discover any new admin account activity which was seen and were not seen historically.\nAny new accounts seen in the results can be validated and investigated for any suspicious activities. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery12", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - SharePointFileOperation via previously unseen IPs", + "elements": [ + { + "name": "huntingquery12-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Shows SharePoint upload/download volume by IPs with high-risk ASNs. New IPs with volume spikes may be unauthorized and exfiltrating documents. This hunting query depends on AzureActiveDirectory AzureActiveDirectory data connector (SigninLogs EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery13", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 -SharePointFileOperation via devices with previously unseen user agents", + "elements": [ + { + "name": "huntingquery13-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Tracking via user agent is one way to differentiate between types of connecting device.\nIn homogeneous enterprise environments the user agent associated with an attacker device may stand out as unusual. This hunting query depends on AzureActiveDirectory AzureActiveDirectory data connector (SigninLogs EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery14", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - New Windows Reserved Filenames staged on Office file services", + "elements": [ + { + "name": "huntingquery14-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This identifies new Windows Reserved Filenames on Office services like SharePoint and OneDrive in the past 7 days. It also detects when a user uploads these files to another user's workspace, which may indicate malicious activity. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery15", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Non-owner mailbox login activity", + "elements": [ + { + "name": "huntingquery15-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Finds non-owner mailbox access by admin/delegate permissions. Whitelist valid users and check others for unauthorized access. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery16", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Office Mail Forwarding - Hunting Version", + "elements": [ + { + "name": "huntingquery16-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Adversaries often abuse email-forwarding rules to monitor victim activities, steal information, and gain intelligence on the victim or their organization. This query highlights cases where user mail is being forwarded, including to external domains. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery17", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - PowerShell or non-browser mailbox login activity", + "elements": [ + { + "name": "huntingquery17-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Detects mailbox login from Exchange PowerShell. All accounts can use it by default, but admins can change it. Whitelist benign activities. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery18", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - SharePoint File Operation via Client IP with Previously Unseen User Agents", + "elements": [ + { + "name": "huntingquery18-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "New user agents associated with a client IP for SharePoint file uploads/downloads. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery19", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Files uploaded to teams and access summary", + "elements": [ + { + "name": "huntingquery19-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This hunting query identifies files uploaded to SharePoint via a Teams chat and\nsummarizes users and IP addresses that have accessed these files. This allows for \nidentification of anomalous file sharing patterns. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery20", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - User added to Teams and immediately uploads file", + "elements": [ + { + "name": "huntingquery20-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This hunting query identifies users who are added to a Teams Channel or Teams chat\nand within 1 minute of being added upload a file via the chat. This might be\nan indicator of suspicious activity. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + }, + { + "name": "huntingquery21", + "type": "Microsoft.Common.Section", + "label": "GSA Enriched Office 365 - Windows Reserved Filenames Staged on Office File Services", + "elements": [ + { + "name": "huntingquery21-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This identifies Windows Reserved Filenames on Office services like SharePoint and OneDrive. It also detects when a user uploads these files to another user's workspace, which may indicate malicious activity. This hunting query depends on AzureActiveDirectory data connector (EnrichedMicrosoft365AuditLogs Parser or Table)" + } + } + ] + } + ] + } + ], + "outputs": { + "workspace-location": "[first(map(filter(basics('getLAWorkspace').value, (filter) => and(contains(toLower(filter.id), toLower(resourceGroup().name)),equals(filter.name,basics('workspace')))), (item) => item.location))]", + "location": "[location()]", + "workspace": "[basics('workspace')]" + } + } +} diff --git a/Solutions/Global Secure Access/Package/mainTemplate.json b/Solutions/Global Secure Access/Package/mainTemplate.json new file mode 100644 index 0000000000..d962276efd --- /dev/null +++ b/Solutions/Global Secure Access/Package/mainTemplate.json @@ -0,0 +1,5024 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "author": "Microsoft - support@microsoft.com", + "comments": "Solution template for Global Secure Access" + }, + "parameters": { + "location": { + "type": "string", + "minLength": 1, + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Not used, but needed to pass arm-ttk test `Location-Should-Not-Be-Hardcoded`. We instead use the `workspace-location` which is derived from the LA workspace" + } + }, + "workspace-location": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "[concat('Region to deploy solution resources -- separate from location selection',parameters('location'))]" + } + }, + "workspace": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Workspace name for Log Analytics where Microsoft Sentinel is setup" + } + }, + "workbook1-name": { + "type": "string", + "defaultValue": "Microsoft Global Secure Access Enriched M365 Logs", + "minLength": 1, + "metadata": { + "description": "Name for the workbook" + } + }, + "workbook2-name": { + "type": "string", + "defaultValue": "Microsoft Global Secure Access Traffic Logs", + "minLength": 1, + "metadata": { + "description": "Name for the workbook" + } + } + }, + "variables": { + "email": "support@microsoft.com", + "_email": "[variables('email')]", + "_solutionName": "Global Secure Access", + "_solutionVersion": "3.0.0", + "solutionId": "azuresentinel.azure-sentinel-solution-globalsecureaccess", + "_solutionId": "[variables('solutionId')]", + "workbookVersion1": "1.0.1", + "workbookContentId1": "GSAM365EnrichedEvents", + "workbookId1": "[resourceId('Microsoft.Insights/workbooks', variables('workbookContentId1'))]", + "workbookTemplateSpecName1": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-wb-',uniquestring(variables('_workbookContentId1'))))]", + "_workbookContentId1": "[variables('workbookContentId1')]", + "workspaceResourceId": "[resourceId('microsoft.OperationalInsights/Workspaces', parameters('workspace'))]", + "_workbookcontentProductId1": "[concat(take(variables('_solutionId'),50),'-','wb','-', uniqueString(concat(variables('_solutionId'),'-','Workbook','-',variables('_workbookContentId1'),'-', variables('workbookVersion1'))))]", + "workbookVersion2": "1.0.1", + "workbookContentId2": "GSANetworkTraffic", + "workbookId2": "[resourceId('Microsoft.Insights/workbooks', variables('workbookContentId2'))]", + "workbookTemplateSpecName2": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-wb-',uniquestring(variables('_workbookContentId2'))))]", + "_workbookContentId2": "[variables('workbookContentId2')]", + "_workbookcontentProductId2": "[concat(take(variables('_solutionId'),50),'-','wb','-', uniqueString(concat(variables('_solutionId'),'-','Workbook','-',variables('_workbookContentId2'),'-', variables('workbookVersion2'))))]", + "analyticRuleObject1": { + "analyticRuleVersion1": "1.0.0", + "_analyticRulecontentId1": "4c9f0a9e-44d7-4c9b-b7f0-f6a6e0d8f8fa", + "analyticRuleId1": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '4c9f0a9e-44d7-4c9b-b7f0-f6a6e0d8f8fa')]", + "analyticRuleTemplateSpecName1": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('4c9f0a9e-44d7-4c9b-b7f0-f6a6e0d8f8fa')))]", + "_analyticRulecontentProductId1": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','4c9f0a9e-44d7-4c9b-b7f0-f6a6e0d8f8fa','-', '1.0.0')))]" + }, + "analyticRuleObject2": { + "analyticRuleVersion2": "1.0.0", + "_analyticRulecontentId2": "57abf863-1c1e-46c6-85b2-35370b712c1e", + "analyticRuleId2": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '57abf863-1c1e-46c6-85b2-35370b712c1e')]", + "analyticRuleTemplateSpecName2": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('57abf863-1c1e-46c6-85b2-35370b712c1e')))]", + "_analyticRulecontentProductId2": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','57abf863-1c1e-46c6-85b2-35370b712c1e','-', '1.0.0')))]" + }, + "analyticRuleObject3": { + "analyticRuleVersion3": "2.0.8", + "_analyticRulecontentId3": "dc451755-8ab3-4059-b805-e454c45d1d44", + "analyticRuleId3": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'dc451755-8ab3-4059-b805-e454c45d1d44')]", + "analyticRuleTemplateSpecName3": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('dc451755-8ab3-4059-b805-e454c45d1d44')))]", + "_analyticRulecontentProductId3": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','dc451755-8ab3-4059-b805-e454c45d1d44','-', '2.0.8')))]" + }, + "analyticRuleObject4": { + "analyticRuleVersion4": "2.1.3", + "_analyticRulecontentId4": "4d38f80f-6b7d-4a1f-aeaf-e38df637e6ac", + "analyticRuleId4": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '4d38f80f-6b7d-4a1f-aeaf-e38df637e6ac')]", + "analyticRuleTemplateSpecName4": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('4d38f80f-6b7d-4a1f-aeaf-e38df637e6ac')))]", + "_analyticRulecontentProductId4": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','4d38f80f-6b7d-4a1f-aeaf-e38df637e6ac','-', '2.1.3')))]" + }, + "analyticRuleObject5": { + "analyticRuleVersion5": "2.1.4", + "_analyticRulecontentId5": "1a8f1297-23a4-4f09-a20b-90af8fc3641a", + "analyticRuleId5": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '1a8f1297-23a4-4f09-a20b-90af8fc3641a')]", + "analyticRuleTemplateSpecName5": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('1a8f1297-23a4-4f09-a20b-90af8fc3641a')))]", + "_analyticRulecontentProductId5": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','1a8f1297-23a4-4f09-a20b-90af8fc3641a','-', '2.1.4')))]" + }, + "analyticRuleObject6": { + "analyticRuleVersion6": "2.1.4", + "_analyticRulecontentId6": "edcfc2e0-3134-434c-8074-9101c530d419", + "analyticRuleId6": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'edcfc2e0-3134-434c-8074-9101c530d419')]", + "analyticRuleTemplateSpecName6": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('edcfc2e0-3134-434c-8074-9101c530d419')))]", + "_analyticRulecontentProductId6": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','edcfc2e0-3134-434c-8074-9101c530d419','-', '2.1.4')))]" + }, + "analyticRuleObject7": { + "analyticRuleVersion7": "2.0.6", + "_analyticRulecontentId7": "a9c76c8d-f60d-49ec-9b1f-bdfee6db3807", + "analyticRuleId7": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'a9c76c8d-f60d-49ec-9b1f-bdfee6db3807')]", + "analyticRuleTemplateSpecName7": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('a9c76c8d-f60d-49ec-9b1f-bdfee6db3807')))]", + "_analyticRulecontentProductId7": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','a9c76c8d-f60d-49ec-9b1f-bdfee6db3807','-', '2.0.6')))]" + }, + "analyticRuleObject8": { + "analyticRuleVersion8": "2.0.6", + "_analyticRulecontentId8": "db60e4b6-a845-4f28-a18c-94ebbaad6c6c", + "analyticRuleId8": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'db60e4b6-a845-4f28-a18c-94ebbaad6c6c')]", + "analyticRuleTemplateSpecName8": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('db60e4b6-a845-4f28-a18c-94ebbaad6c6c')))]", + "_analyticRulecontentProductId8": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','db60e4b6-a845-4f28-a18c-94ebbaad6c6c','-', '2.0.6')))]" + }, + "analyticRuleObject9": { + "analyticRuleVersion9": "2.0.5", + "_analyticRulecontentId9": "d75e8289-d1cb-44d4-bd59-2f44a9172478", + "analyticRuleId9": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'd75e8289-d1cb-44d4-bd59-2f44a9172478')]", + "analyticRuleTemplateSpecName9": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('d75e8289-d1cb-44d4-bd59-2f44a9172478')))]", + "_analyticRulecontentProductId9": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','d75e8289-d1cb-44d4-bd59-2f44a9172478','-', '2.0.5')))]" + }, + "analyticRuleObject10": { + "analyticRuleVersion10": "2.0.6", + "_analyticRulecontentId10": "0f1f2b17-f9d6-4d2a-a0fb-a7ae1659e3eb", + "analyticRuleId10": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '0f1f2b17-f9d6-4d2a-a0fb-a7ae1659e3eb')]", + "analyticRuleTemplateSpecName10": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('0f1f2b17-f9d6-4d2a-a0fb-a7ae1659e3eb')))]", + "_analyticRulecontentProductId10": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','0f1f2b17-f9d6-4d2a-a0fb-a7ae1659e3eb','-', '2.0.6')))]" + }, + "analyticRuleObject11": { + "analyticRuleVersion11": "2.0.7", + "_analyticRulecontentId11": "178c62b4-d5e5-40f5-8eab-7fccd0051e7a", + "analyticRuleId11": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '178c62b4-d5e5-40f5-8eab-7fccd0051e7a')]", + "analyticRuleTemplateSpecName11": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('178c62b4-d5e5-40f5-8eab-7fccd0051e7a')))]", + "_analyticRulecontentProductId11": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','178c62b4-d5e5-40f5-8eab-7fccd0051e7a','-', '2.0.7')))]" + }, + "analyticRuleObject12": { + "analyticRuleVersion12": "2.0.7", + "_analyticRulecontentId12": "433c254d-4b84-46f7-99ec-9dfefb5f6a7b", + "analyticRuleId12": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '433c254d-4b84-46f7-99ec-9dfefb5f6a7b')]", + "analyticRuleTemplateSpecName12": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('433c254d-4b84-46f7-99ec-9dfefb5f6a7b')))]", + "_analyticRulecontentProductId12": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','433c254d-4b84-46f7-99ec-9dfefb5f6a7b','-', '2.0.7')))]" + }, + "analyticRuleObject13": { + "analyticRuleVersion13": "2.0.6", + "_analyticRulecontentId13": "7460e34e-4c99-47b2-b7c0-c42e339fc586", + "analyticRuleId13": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '7460e34e-4c99-47b2-b7c0-c42e339fc586')]", + "analyticRuleTemplateSpecName13": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('7460e34e-4c99-47b2-b7c0-c42e339fc586')))]", + "_analyticRulecontentProductId13": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','7460e34e-4c99-47b2-b7c0-c42e339fc586','-', '2.0.6')))]" + }, + "analyticRuleObject14": { + "analyticRuleVersion14": "2.2.6", + "_analyticRulecontentId14": "efd17c5f-5167-40f8-a1e9-0818940785d9", + "analyticRuleId14": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'efd17c5f-5167-40f8-a1e9-0818940785d9')]", + "analyticRuleTemplateSpecName14": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('efd17c5f-5167-40f8-a1e9-0818940785d9')))]", + "_analyticRulecontentProductId14": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','efd17c5f-5167-40f8-a1e9-0818940785d9','-', '2.2.6')))]" + }, + "analyticRuleObject15": { + "analyticRuleVersion15": "1.0.5", + "_analyticRulecontentId15": "30375d00-68cc-4f95-b89a-68064d566358", + "analyticRuleId15": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '30375d00-68cc-4f95-b89a-68064d566358')]", + "analyticRuleTemplateSpecName15": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('30375d00-68cc-4f95-b89a-68064d566358')))]", + "_analyticRulecontentProductId15": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','30375d00-68cc-4f95-b89a-68064d566358','-', '1.0.5')))]" + }, + "analyticRuleObject16": { + "analyticRuleVersion16": "2.0.8", + "_analyticRulecontentId16": "abd6976d-8f71-4851-98c4-4d086201319c", + "analyticRuleId16": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'abd6976d-8f71-4851-98c4-4d086201319c')]", + "analyticRuleTemplateSpecName16": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('abd6976d-8f71-4851-98c4-4d086201319c')))]", + "_analyticRulecontentProductId16": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','abd6976d-8f71-4851-98c4-4d086201319c','-', '2.0.8')))]" + }, + "analyticRuleObject17": { + "analyticRuleVersion17": "1.0.0", + "_analyticRulecontentId17": "e3b6a9e7-4c3a-45e6-8baf-1d3bfa8e0c2b", + "analyticRuleId17": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'e3b6a9e7-4c3a-45e6-8baf-1d3bfa8e0c2b')]", + "analyticRuleTemplateSpecName17": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('e3b6a9e7-4c3a-45e6-8baf-1d3bfa8e0c2b')))]", + "_analyticRulecontentProductId17": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','e3b6a9e7-4c3a-45e6-8baf-1d3bfa8e0c2b','-', '1.0.0')))]" + }, + "analyticRuleObject18": { + "analyticRuleVersion18": "1.0.0", + "_analyticRulecontentId18": "f6a8d6a5-3e9f-47c8-a8d5-1b2b9d3b7d6a", + "analyticRuleId18": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', 'f6a8d6a5-3e9f-47c8-a8d5-1b2b9d3b7d6a')]", + "analyticRuleTemplateSpecName18": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('f6a8d6a5-3e9f-47c8-a8d5-1b2b9d3b7d6a')))]", + "_analyticRulecontentProductId18": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','f6a8d6a5-3e9f-47c8-a8d5-1b2b9d3b7d6a','-', '1.0.0')))]" + }, + "analyticRuleObject19": { + "analyticRuleVersion19": "1.0.0", + "_analyticRulecontentId19": "82cfa6b9-5f7e-4b8b-8b2f-a63f21b7a7d1", + "analyticRuleId19": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '82cfa6b9-5f7e-4b8b-8b2f-a63f21b7a7d1')]", + "analyticRuleTemplateSpecName19": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('82cfa6b9-5f7e-4b8b-8b2f-a63f21b7a7d1')))]", + "_analyticRulecontentProductId19": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','82cfa6b9-5f7e-4b8b-8b2f-a63f21b7a7d1','-', '1.0.0')))]" + }, + "huntingQueryObject1": { + "huntingQueryVersion1": "2.0.2", + "_huntingQuerycontentId1": "271e8881-3044-4332-a5f4-42264c2e0315", + "huntingQueryTemplateSpecName1": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('271e8881-3044-4332-a5f4-42264c2e0315')))]" + }, + "huntingQueryObject2": { + "huntingQueryVersion2": "2.0.1", + "_huntingQuerycontentId2": "d12580c2-1474-4125-a8a3-553f50d91215", + "huntingQueryTemplateSpecName2": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('d12580c2-1474-4125-a8a3-553f50d91215')))]" + }, + "huntingQueryObject3": { + "huntingQueryVersion3": "2.0.1", + "_huntingQuerycontentId3": "119d9e1c-afcc-4d23-b239-cdb4e7bf851c", + "huntingQueryTemplateSpecName3": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('119d9e1c-afcc-4d23-b239-cdb4e7bf851c')))]" + }, + "huntingQueryObject4": { + "huntingQueryVersion4": "2.0.2", + "_huntingQuerycontentId4": "6fce5baf-bfc2-4c56-a6b7-9c4733fc5a45", + "huntingQueryTemplateSpecName4": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('6fce5baf-bfc2-4c56-a6b7-9c4733fc5a45')))]" + }, + "huntingQueryObject5": { + "huntingQueryVersion5": "2.0.2", + "_huntingQuerycontentId5": "9891684a-1e3a-4546-9403-3439513cbc70", + "huntingQueryTemplateSpecName5": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('9891684a-1e3a-4546-9403-3439513cbc70')))]" + }, + "huntingQueryObject6": { + "huntingQueryVersion6": "2.0.2", + "_huntingQuerycontentId6": "64990414-b015-4edf-bef0-343b741e68c5", + "huntingQueryTemplateSpecName6": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('64990414-b015-4edf-bef0-343b741e68c5')))]" + }, + "huntingQueryObject7": { + "huntingQueryVersion7": "2.0.2", + "_huntingQuerycontentId7": "a1551ae4-f61c-4bca-9c57-4d0d681db2e9", + "huntingQueryTemplateSpecName7": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('a1551ae4-f61c-4bca-9c57-4d0d681db2e9')))]" + }, + "huntingQueryObject8": { + "huntingQueryVersion8": "2.0.1", + "_huntingQuerycontentId8": "9eb64924-ec8d-44d0-b1f2-10665150fb74", + "huntingQueryTemplateSpecName8": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('9eb64924-ec8d-44d0-b1f2-10665150fb74')))]" + }, + "huntingQueryObject9": { + "huntingQueryVersion9": "2.0.2", + "_huntingQuerycontentId9": "558f15dd-3171-4b11-bf24-31c0610a20e0", + "huntingQueryTemplateSpecName9": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('558f15dd-3171-4b11-bf24-31c0610a20e0')))]" + }, + "huntingQueryObject10": { + "huntingQueryVersion10": "2.0.2", + "_huntingQuerycontentId10": "bf76e508-9282-4cf1-9cc1-5c20c3dea2ee", + "huntingQueryTemplateSpecName10": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('bf76e508-9282-4cf1-9cc1-5c20c3dea2ee')))]" + }, + "huntingQueryObject11": { + "huntingQueryVersion11": "2.0.1", + "_huntingQuerycontentId11": "723c5f46-133f-4f1e-ada6-5c138f811d75", + "huntingQueryTemplateSpecName11": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('723c5f46-133f-4f1e-ada6-5c138f811d75')))]" + }, + "huntingQueryObject12": { + "huntingQueryVersion12": "2.0.2", + "_huntingQuerycontentId12": "e3d24cfd-b2a1-4ba7-8f80-0360892f9d57", + "huntingQueryTemplateSpecName12": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('e3d24cfd-b2a1-4ba7-8f80-0360892f9d57')))]" + }, + "huntingQueryObject13": { + "huntingQueryVersion13": "2.0.2", + "_huntingQuerycontentId13": "f2367171-1514-4c67-88ef-27434b6a1093", + "huntingQueryTemplateSpecName13": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('f2367171-1514-4c67-88ef-27434b6a1093')))]" + }, + "huntingQueryObject14": { + "huntingQueryVersion14": "2.0.2", + "_huntingQuerycontentId14": "641ecd2d-27c9-4f05-8433-8205096b09fc", + "huntingQueryTemplateSpecName14": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('641ecd2d-27c9-4f05-8433-8205096b09fc')))]" + }, + "huntingQueryObject15": { + "huntingQueryVersion15": "2.0.1", + "_huntingQuerycontentId15": "0a8f410d-38b5-4d75-90da-32b472b97230", + "huntingQueryTemplateSpecName15": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('0a8f410d-38b5-4d75-90da-32b472b97230')))]" + }, + "huntingQueryObject16": { + "huntingQueryVersion16": "2.0.2", + "_huntingQuerycontentId16": "d49fc965-aef3-49f6-89ad-10cc4697eb5b", + "huntingQueryTemplateSpecName16": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('d49fc965-aef3-49f6-89ad-10cc4697eb5b')))]" + }, + "huntingQueryObject17": { + "huntingQueryVersion17": "2.0.2", + "_huntingQuerycontentId17": "49a4f65a-fe18-408e-afec-042fde93d3ce", + "huntingQueryTemplateSpecName17": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('49a4f65a-fe18-408e-afec-042fde93d3ce')))]" + }, + "huntingQueryObject18": { + "huntingQueryVersion18": "2.0.2", + "_huntingQuerycontentId18": "e8ae1375-4640-430c-ae8e-2514d09c71eb", + "huntingQueryTemplateSpecName18": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('e8ae1375-4640-430c-ae8e-2514d09c71eb')))]" + }, + "huntingQueryObject19": { + "huntingQueryVersion19": "2.0.2", + "_huntingQuerycontentId19": "90e198a9-efb6-4719-ad89-81b8e93633a7", + "huntingQueryTemplateSpecName19": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('90e198a9-efb6-4719-ad89-81b8e93633a7')))]" + }, + "huntingQueryObject20": { + "huntingQueryVersion20": "2.0.2", + "_huntingQuerycontentId20": "3d6d0c04-7337-40cf-ace6-c471d442356d", + "huntingQueryTemplateSpecName20": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('3d6d0c04-7337-40cf-ace6-c471d442356d')))]" + }, + "huntingQueryObject21": { + "huntingQueryVersion21": "2.0.2", + "_huntingQuerycontentId21": "61c28cd7-3139-4731-8ea7-2cbbeabb4684", + "huntingQueryTemplateSpecName21": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-hq-',uniquestring('61c28cd7-3139-4731-8ea7-2cbbeabb4684')))]" + }, + "_solutioncontentProductId": "[concat(take(variables('_solutionId'),50),'-','sl','-', uniqueString(concat(variables('_solutionId'),'-','Solution','-',variables('_solutionId'),'-', variables('_solutionVersion'))))]" + }, + "resources": [ + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('workbookTemplateSpecName1')]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "GSAM365EnrichedEvents Workbook with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('workbookVersion1')]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Insights/workbooks", + "name": "[variables('workbookContentId1')]", + "location": "[parameters('workspace-location')]", + "kind": "shared", + "apiVersion": "2021-08-01", + "metadata": { + "description": "This Workbook provides a detailed view of Microsoft 365 log data, enriched with contextual information to enhance visibility into user activities and potential security threats." + }, + "properties": { + "displayName": "[parameters('workbook1-name')]", + "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"## Enriched Microsoft 365 logs Workbook (Preview)\\n---\\n\\nThe enriched Microsoft 365 logs provide information about Microsoft 365 workloads, so you can review network data and security events relevant to Microsoft 365 apps.\"},\"name\":\"text - 2\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"value::all\"],\"parameters\":[{\"id\":\"ff8b2a55-1849-4848-acf8-eab5452e9f10\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"LogAnalyticWorkspace\",\"label\":\"Log Analytic Workspace\",\"type\":5,\"description\":\"The Log Analytic Workspace In Which To Execute The Queries\",\"isRequired\":true,\"query\":\"resources\\r\\n| where type == \\\"microsoft.operationalinsights/workspaces\\\"\\r\\n| project id\",\"crossComponentResources\":[\"value::all\"],\"typeSettings\":{\"resourceTypeFilter\":{\"microsoft.operationalinsights/workspaces\":true},\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},{\"id\":\"f15f34d8-8e2d-4c39-8dee-be2f979c86a8\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TimeRange\",\"label\":\"Time Range\",\"type\":4,\"isRequired\":true,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":300000},{\"durationMs\":900000},{\"durationMs\":1800000},{\"durationMs\":3600000},{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000}],\"allowCustom\":true},\"timeContext\":{\"durationMs\":86400000},\"value\":{\"durationMs\":2592000000}},{\"id\":\"8bab511b-53b3-4220-9d1c-372345b06728\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Users\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| summarize Count = count() by UserId\\r\\n| order by Count desc, UserId asc\\r\\n| project Value = UserId, Label = strcat(UserId, ' - ', Count, ' Logs'), Selected = false\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"typeSettings\":{\"limitSelectTo\":20,\"additionalResourceOptions\":[\"value::all\"],\"selectAllValue\":\"*\",\"showDefault\":false},\"timeContext\":{\"durationMs\":0},\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":[\"value::all\"]}],\"style\":\"pills\",\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},\"name\":\"parameters - 15\"},{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"tabs\",\"tabStyle\":\"bigger\",\"links\":[{\"id\":\"e841bafb-6437-4d29-84ac-ba16c5a6d901\",\"cellValue\":\"selTab\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Overview\",\"subTarget\":\"Overview\",\"style\":\"link\"},{\"id\":\"ac5f2082-50bc-4739-bdf2-20c93b613671\",\"cellValue\":\"selTab\",\"linkTarget\":\"parameter\",\"linkLabel\":\"SharePoint/OneDrive Insights\",\"subTarget\":\"Threat\",\"style\":\"link\"},{\"id\":\"dc2778e7-739b-44ba-9ae4-c81901277f57\",\"cellValue\":\"selTab\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Exchange Online Insights\",\"subTarget\":\"ThreatEXO\",\"style\":\"link\"},{\"id\":\"666111e2-54ff-4fa4-a648-11a5c8c0235b\",\"cellValue\":\"selTab\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Teams Insights\",\"subTarget\":\"ThreatTeams\",\"style\":\"link\"}]},\"name\":\"links - 7\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| where Workload in (\\\"Exchange\\\")\\r\\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\\r\\n| extend \\r\\n Country = tostring(GeoInfo.country), \\r\\n State = tostring(GeoInfo.state), \\r\\n City = tostring(GeoInfo.city), \\r\\n Latitude = tostring(GeoInfo.latitude), \\r\\n Longitude = tostring(GeoInfo.longitude)\\r\\n| project \\r\\n UserId, \\r\\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\\r\\n Latitude, \\r\\n Longitude, \\r\\n City, \\r\\n State, \\r\\n Country\\r\\n| summarize Count = count() by City, State, Country\\r\\n\",\"size\":0,\"title\":\"Access By Location\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"Country\",\"latitude\":\"Latitude\",\"longitude\":\"Longitude\",\"sizeSettings\":\"Count\",\"sizeAggregation\":\"Sum\",\"labelSettings\":\"Country\",\"legendMetric\":\"Count\",\"legendAggregation\":\"Sum\",\"itemColorSettings\":{\"nodeColorField\":\"Count\",\"colorAggregation\":\"Sum\",\"type\":\"heatmap\",\"heatmapPalette\":\"turquoise\"}}},\"customWidth\":\"50\",\"name\":\"query - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| where Workload in (\\\"Exchange\\\")\\r\\n| summarize [\\\"Count\\\"] = count() by [\\\"Source IP\\\"] = SourceIp, [\\\"Device Operating System\\\"] = DeviceOperatingSystem\\r\\n| order by [\\\"Count\\\"] desc\\r\\n\",\"size\":0,\"title\":\"Access By Location And OS\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Operation in (\\\"Set-AdminAuditLogConfig\\\", \\\"Set-OrganizationConfig\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Audit Of Critical Configuration Changes\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Operation in (\\\"Set-TransportRule\\\", \\\"Set-AtpPolicyForO365\\\", \\\"Set-MalwareFilterRule\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Changes In Email Security Policies\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 3\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Operation in (\\\"Add-RoleGroupMember\\\", \\\"Remove-ManagementRoleAssignment\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Monitoring Role Group Membership Changes\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 4\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Operation in (\\\"New-TenantAllowBlockListSpoofitems\\\", \\\"Remove-TenantAllowBlockListSpoofitems\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Spoofing Settings Management Activities\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 5\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Operation in (\\\"New-SafeAttachmentPolicy\\\", \\\"Set-SafeAttachmentPolicy\\\", \\\"Set-SafeAttachmentRule\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Safe Attachments Policy Changes\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 6\"}]},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"ThreatEXO\"},\"name\":\"group - 18\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs | where Workload == \\\"Exchange\\\" | where Operation in (\\\"Add-MailboxFolderPermission\\\", \\\"Add-RoleGroupMember\\\", \\\"New-TransportRule\\\", \\\"Remove-ManagementRoleAssignment\\\", \\\"Set-TransportRule\\\") | summarize Count = count(), Users = makeset(UserId) by Operation | order by Count desc\",\"size\":0,\"title\":\"Permission changes and security policy updates\",\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"conditionalVisibility\":{\"parameterName\":\"seltab\",\"comparison\":\"isEqualTo\",\"value\":\"ThreatEXO\"},\"name\":\"query - 13\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| where Workload in (\\\"Teams\\\")\\r\\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\\r\\n| extend \\r\\n Country = tostring(GeoInfo.country), \\r\\n State = tostring(GeoInfo.state), \\r\\n City = tostring(GeoInfo.city), \\r\\n Latitude = tostring(GeoInfo.latitude), \\r\\n Longitude = tostring(GeoInfo.longitude)\\r\\n| project \\r\\n UserId, \\r\\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\\r\\n Latitude, \\r\\n Longitude, \\r\\n City, \\r\\n State, \\r\\n Country\\r\\n| summarize Count = count() by City, State, Country\\r\\n\",\"size\":0,\"title\":\"Access By Location\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"Country\",\"latitude\":\"Latitude\",\"longitude\":\"Longitude\",\"sizeSettings\":\"Count\",\"sizeAggregation\":\"Sum\",\"labelSettings\":\"Country\",\"legendMetric\":\"Count\",\"legendAggregation\":\"Sum\",\"itemColorSettings\":{\"nodeColorField\":\"Count\",\"colorAggregation\":\"Sum\",\"type\":\"heatmap\",\"heatmapPalette\":\"turquoise\"}}},\"customWidth\":\"50\",\"name\":\"query - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| where Workload in (\\\"Teams\\\")\\r\\n| summarize [\\\"Count\\\"] = count() by [\\\"Source IP\\\"] = SourceIp, [\\\"Device Operating System\\\"] = DeviceOperatingSystem\\r\\n| order by [\\\"Count\\\"] desc\\r\\n\",\"size\":0,\"title\":\"Access By Location And OS\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"customWidth\":\"50\",\"name\":\"query - 1\"}]},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"ThreatTeams\"},\"name\":\"group - 10\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| where Workload in (\\\"OneDrive\\\", \\\"SharePoint\\\",\\\"SPO/OneDrive\\\")\\r\\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\\r\\n| extend \\r\\n Country = tostring(GeoInfo.country), \\r\\n State = tostring(GeoInfo.state), \\r\\n City = tostring(GeoInfo.city), \\r\\n Latitude = tostring(GeoInfo.latitude), \\r\\n Longitude = tostring(GeoInfo.longitude)\\r\\n| project \\r\\n UserId, \\r\\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\\r\\n Latitude, \\r\\n Longitude, \\r\\n City, \\r\\n State, \\r\\n Country\\r\\n| summarize Count = count() by City, State, Country\\r\\n\",\"size\":0,\"title\":\"Access By Location\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"Country\",\"latitude\":\"Latitude\",\"longitude\":\"Longitude\",\"sizeSettings\":\"Count\",\"sizeAggregation\":\"Sum\",\"labelSettings\":\"Country\",\"legendMetric\":\"Country\",\"numberOfMetrics\":19,\"legendAggregation\":\"Count\",\"itemColorSettings\":{\"nodeColorField\":\"Count\",\"colorAggregation\":\"Sum\",\"type\":\"heatmap\",\"heatmapPalette\":\"turquoise\"}}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Threat\"},\"name\":\"query - 19\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| where Workload in (\\\"OneDrive\\\", \\\"SharePoint\\\", \\\"SPO/OneDrive\\\")\\r\\n| summarize [\\\"Event Count\\\"] = count() by [\\\"Source IP\\\"] = SourceIp, [\\\"Device Operating System\\\"] = DeviceOperatingSystem\\r\\n| order by [\\\"Event Count\\\"] desc\\r\\n\",\"size\":2,\"title\":\"Access By Location And OS\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"filter\":true}},\"customWidth\":\"50\",\"name\":\"query - 20\"}]},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Threat\"},\"name\":\"group - 20\",\"styleSettings\":{\"showBorder\":true}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Workload == \\\"Teams\\\"\\r\\n| where Operation in (\\\"MemberAdded\\\", \\\"MemberRemoved\\\", \\\"TeamDeleted\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Changes In Team Memberships\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"ThreatTeams\"},\"customWidth\":\"50\",\"name\":\"query - 11\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let RiskyExtensions = dynamic([\\\"exe\\\", \\\"msi\\\", \\\"bat\\\", \\\"cmd\\\", \\\"com\\\", \\\"scr\\\", \\\"pif\\\", \\\"ps1\\\", \\\"vbs\\\", \\\"js\\\", \\\"jse\\\", \\\"wsf\\\", \\\"docm\\\", \\\"xlsm\\\", \\\"pptm\\\", \\\"dll\\\", \\\"ocx\\\", \\\"cpl\\\", \\\"app\\\", \\\"vb\\\", \\\"reg\\\", \\\"inf\\\", \\\"hta\\\"]);\\r\\n\\r\\nEnrichedMicrosoft365AuditLogs\\r\\n| where Workload in (\\\"SPO/OneDrive\\\")\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| extend [\\\"File Extension\\\"] = tostring(parse_json(AdditionalProperties).SourceFileExtension)\\r\\n| where [\\\"File Extension\\\"] in (RiskyExtensions)\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, [\\\"File Extension\\\"], Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], [\\\"File Extension\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], [\\\"File Extension\\\"], Operation, Count\\r\\n\",\"size\":0,\"title\":\"Risky File Operations\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"OperationCount\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\"}}]}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Threat\"},\"name\":\"query - 1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Workload in (\\\"OneDrive\\\", \\\"SharePoint\\\", \\\"SPO/OneDrive\\\")\\r\\n| where Operation in (\\\"FileRecycled\\\", \\\"FileDownloaded\\\", \\\"FileUploaded\\\", \\\"FileCreated\\\", \\\"File Modified\\\")\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Bulk File Events\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"OperationCount\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\"}}]}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Threat\"},\"name\":\"query - 8\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where Workload in (\\\"SPO/OneDrive\\\")\\r\\n| where Operation == \\\"FileDeletedFirstStageRecycleBin\\\"\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by bin(Timestamp, 1h), [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n| where Count > 1\\r\\n\",\"size\":0,\"title\":\" Bulk File Deletion Operations\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"FileDeletions\",\"formatter\":8,\"formatOptions\":{\"palette\":\"blue\"}}]}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Threat\"},\"name\":\"query - 3\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let baselinePeriod = 30d;\\r\\nlet detectionWindow = 1h;\\r\\nlet downloadThreshold = 5; // Threshold of downloads indicating potential exfiltration\\r\\n\\r\\nEnrichedMicrosoft365AuditLogs\\r\\n| where TimeGenerated >= ago(baselinePeriod)\\r\\n| where Workload in (\\\"OneDrive\\\", \\\"SharePoint\\\", \\\"SPO/OneDrive\\\")\\r\\n| where Operation == \\\"FileDownloaded\\\"\\r\\n| project Timestamp = TimeGenerated, [\\\"User ID\\\"] = UserId, [\\\"Client IP\\\"] = ClientIp, Operation\\r\\n| summarize Count = count() by bin(Timestamp, detectionWindow), [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation\\r\\n| project Timestamp, [\\\"User ID\\\"], [\\\"Client IP\\\"], Operation, Count\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Bulk File Download\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"DownloadCount\",\"formatter\":8,\"formatOptions\":{\"palette\":\"blue\"}}]}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Threat\"},\"name\":\"query - 4\"}]},\"name\":\"group - 9\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\\r\\n| extend \\r\\n Country = tostring(GeoInfo.country), \\r\\n State = tostring(GeoInfo.state), \\r\\n City = tostring(GeoInfo.city), \\r\\n Latitude = tostring(GeoInfo.latitude), \\r\\n Longitude = tostring(GeoInfo.longitude)\\r\\n| project \\r\\n UserId, \\r\\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\\r\\n Latitude, \\r\\n Longitude, \\r\\n City, \\r\\n State, \\r\\n Country\\r\\n | where tostring(Country) != \\\"\\\"\\r\\n| summarize Count = count() by City, State, Country\\r\\n\\r\\n\\r\\n\",\"size\":0,\"title\":\"Access By Location\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"Country\",\"latitude\":\"Latitude\",\"longitude\":\"Longitude\",\"sizeSettings\":\"Count\",\"sizeAggregation\":\"Sum\",\"minSize\":20,\"labelSettings\":\"Country\",\"legendMetric\":\"Country\",\"numberOfMetrics\":8,\"legendAggregation\":\"Count\",\"itemColorSettings\":{\"nodeColorField\":\"Count\",\"colorAggregation\":\"Sum\",\"type\":\"heatmap\",\"heatmapPalette\":\"turquoise\"}}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 5\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| summarize [\\\"Event Count\\\"] = count() by [\\\"Time Generated\\\"] = TimeGenerated, [\\\"User\\\"] = UserId, [\\\"Source IP\\\"] = SourceIp, [\\\"Device Operating System\\\"] = DeviceOperatingSystem, [\\\"Workload\\\"] = Workload\\r\\n| order by [\\\"Time Generated\\\"] desc\\r\\n\",\"size\":0,\"title\":\"Access By Location And OS\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"rowLimit\":1000,\"filter\":true}},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 1\"}]},\"name\":\"group - 19\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let data = EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| project \\r\\n Operation, \\r\\n UserId, \\r\\n Workload, \\r\\n SourceIp, \\r\\n DeviceId, \\r\\n TimeGenerated, \\r\\n Details = pack_all(),\\r\\n OS = tostring(DeviceOperatingSystem)\\r\\n| extend Workload = tostring(Workload)\\r\\n| extend WorkloadStatus = case(\\r\\n Workload == \\\"Exchange\\\", \\\"Exchange\\\",\\r\\n Workload == \\\"Teams\\\", \\\"Teams\\\",\\r\\n Workload == \\\"SharePoint\\\", \\\"SharePoint\\\",\\r\\n \\\"Other\\\"\\r\\n);\\r\\n\\r\\nlet appData = data\\r\\n| summarize \\r\\n TotalCount = count(), \\r\\n ExchangeCount = countif(Workload == \\\"Exchange\\\"), \\r\\n TeamsCount = countif(Workload == \\\"Teams\\\"), \\r\\n SharePointCount = countif(Workload == \\\"SharePoint\\\"), \\r\\n OtherCount = countif(Workload == \\\"Other\\\") \\r\\n by UserId\\r\\n| where UserId != ''\\r\\n| join kind=inner (\\r\\n data\\r\\n | make-series Trend = count() default = 0 on TimeGenerated in range({TimeRange:start}, {TimeRange:end}, {TimeRange:grain}) by UserId\\r\\n | project-away TimeGenerated\\r\\n ) on UserId\\r\\n| order by TotalCount desc, UserId asc\\r\\n| project UserId, TotalCount, ExchangeCount, TeamsCount, SharePointCount, OtherCount, Trend\\r\\n| serialize Id = row_number();\\r\\n\\r\\ndata\\r\\n| summarize \\r\\n TotalCount = count(), \\r\\n ExchangeCount = countif(Workload == \\\"Exchange\\\"), \\r\\n TeamsCount = countif(Workload == \\\"Teams\\\"), \\r\\n SharePointCount = countif(Workload == \\\"SharePoint\\\"), \\r\\n OtherCount = countif(Workload == \\\"Other\\\") \\r\\n by UserId, SourceIp = tostring(SourceIp)\\r\\n| join kind=inner (\\r\\n data\\r\\n | make-series Trend = count() default = 0 on TimeGenerated in range({TimeRange:start}, {TimeRange:end}, {TimeRange:grain}) by UserId, SourceIp\\r\\n | project-away TimeGenerated\\r\\n ) on UserId, SourceIp\\r\\n| order by TotalCount desc, UserId asc\\r\\n| project UserId, SourceIp, TotalCount, ExchangeCount, TeamsCount, SharePointCount, OtherCount, Trend\\r\\n| serialize Id = row_number(1000000)\\r\\n| join kind=inner (appData) on UserId\\r\\n| project Id, Name = SourceIp, Type = 'Client IP', ['Events Count'] = TotalCount, Trend, ['Exchange Events'] = ExchangeCount, ['Teams Events'] = TeamsCount, ['SharePoint Events'] = SharePointCount, ['Other Count'] = OtherCount, ParentId = Id1\\r\\n| union (\\r\\n appData \\r\\n | project Id, Name = UserId, Type = 'Operating System', ['Events Count'] = TotalCount, Trend, ['Exchange Events'] = ExchangeCount, ['Teams Events'] = TeamsCount, ['SharePoint Events'] = SharePointCount, ['Other Count'] = OtherCount, ParentId = -1\\r\\n )\\r\\n| order by ['Events Count'] desc, Name asc\\r\\n\",\"size\":2,\"title\":\"Activity Log\",\"timeContextFromParameter\":\"TimeRange\",\"showRefreshButton\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Id\",\"formatter\":5},{\"columnMatch\":\"Type\",\"formatter\":5},{\"columnMatch\":\"Trend\",\"formatter\":9,\"formatOptions\":{\"palette\":\"blue\"}},{\"columnMatch\":\"Other Count\",\"formatter\":5},{\"columnMatch\":\"ParentId\",\"formatter\":5},{\"columnMatch\":\"Operation Count\",\"formatter\":8,\"formatOptions\":{\"palette\":\"blue\"}},{\"columnMatch\":\"Exchange Count\",\"formatter\":8,\"formatOptions\":{\"min\":0,\"palette\":\"blue\"}},{\"columnMatch\":\"Teams Count\",\"formatter\":8,\"formatOptions\":{\"min\":0,\"palette\":\"purple\"}},{\"columnMatch\":\"SharePoint Count\",\"formatter\":8,\"formatOptions\":{\"min\":0,\"palette\":\"turquoise\"}},{\"columnMatch\":\"Details\",\"formatter\":5,\"formatOptions\":{\"linkTarget\":\"GenericDetails\",\"linkIsContextBlade\":true}}],\"rowLimit\":1000,\"filter\":true,\"hierarchySettings\":{\"idColumn\":\"Id\",\"parentColumn\":\"ParentId\",\"treeType\":0,\"expanderColumn\":\"Name\"}}},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 6\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":1,\"content\":{\"json\":\"
\\r\\nšŸ’” _Click on a segment of the pie chart to explore more details_\"},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"text - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| extend \\r\\n OS = coalesce(DeviceOperatingSystem, \\\"Unknown OS\\\"),\\r\\n OSVersion = coalesce(tostring(DeviceOperatingSystemVersion), \\\"Unknown Version\\\")\\r\\n| summarize DeviceCount = count() by OS, OSVersion\\r\\n| order by DeviceCount desc\\r\\n\",\"size\":3,\"title\":\"Devices Accessing M365\",\"timeContextFromParameter\":\"TimeRange\",\"exportParameterName\":\"Parampie\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"piechart\"},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| extend \\r\\n [\\\"Operating System\\\"] = coalesce(DeviceOperatingSystem, \\\"Unknown OS\\\"),\\r\\n [\\\"OS Version\\\"] = coalesce(tostring(DeviceOperatingSystemVersion), \\\"Unknown Version\\\"),\\r\\n [\\\"Device ID\\\"] = coalesce(tostring(DeviceId), \\\"Unknown DeviceId\\\")\\r\\n| where [\\\"Operating System\\\"] == dynamic({Parampie}).label\\r\\n| summarize [\\\"Device Count\\\"] = count() by [\\\"Operating System\\\"], [\\\"OS Version\\\"], [\\\"Device ID\\\"]\\r\\n| order by [\\\"Device Count\\\"] desc\\r\\n\",\"size\":2,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"$gen_group\",\"formatter\":1}],\"hierarchySettings\":{\"treeType\":1,\"groupBy\":[\"OperatingSystem\",\"OSVersion\"],\"expandTopLevel\":false}}},\"customWidth\":\"50\",\"conditionalVisibilities\":[{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},{\"parameterName\":\"Parampie\",\"comparison\":\"isNotEqualTo\"}],\"name\":\"query - 1\"}]},\"name\":\"group - 19\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let BusinessHoursStart = 8; // 8 AM\\r\\nlet BusinessHoursEnd = 18; // 6 PM\\r\\n\\r\\nEnrichedMicrosoft365AuditLogs \\r\\n| where UserId in ({Users}) or '*' in ({Users})\\r\\n| extend HourOfDay = hourofday(TimeGenerated)\\r\\n| where HourOfDay < BusinessHoursStart or HourOfDay > BusinessHoursEnd\\r\\n| summarize [\\\"Off-Hour Activities\\\"] = count() by [\\\"User ID\\\"] = UserId, [\\\"Date\\\"] = bin(TimeGenerated, 1d), [\\\"Operation\\\"]\\r\\n| order by [\\\"Off-Hour Activities\\\"] desc\\r\\n\",\"size\":0,\"title\":\"Activity Outside Standard Working Hours (8:00 - 18:00)\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 14\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"EnrichedMicrosoft365AuditLogs\\n| summarize Count = count() by bin(TimeGenerated, 1h)\\n| order by TimeGenerated asc\\n\",\"size\":0,\"title\":\"Microsoft 365 Transactions\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"unstackedbar\"},\"conditionalVisibility\":{\"parameterName\":\"selTab\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 2\"}],\"fallbackResourceIds\":[\"Global Secure Access\"],\"fromTemplateId\":\"GSA Enriched Microsoft 365 logs\",\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", + "version": "1.0", + "sourceId": "[variables('workspaceResourceId')]", + "category": "sentinel" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('Workbook-', last(split(variables('workbookId1'),'/'))))]", + "properties": { + "description": "@{workbookKey=GSAM365EnrichedEvents; logoFileName=gsa.svg; description=This Workbook provides a detailed view of Microsoft 365 log data, enriched with contextual information to enhance visibility into user activities and potential security threats.; dataTypesDependencies=System.Object[]; dataConnectorsDependencies=System.Object[]; previewImagesFileNames=System.Object[]; version=1.0.1; title=Microsoft Global Secure Access Enriched M365 Logs; templateRelativePath=GSAM365EnrichedEvents.json; provider=Microsoft}.description", + "parentId": "[variables('workbookId1')]", + "contentId": "[variables('_workbookContentId1')]", + "kind": "Workbook", + "version": "[variables('workbookVersion1')]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + }, + "dependencies": { + "operator": "AND", + "criteria": [ + { + "contentId": "AzureActiveDirectory", + "kind": "DataConnector" + } + ] + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('_workbookContentId1')]", + "contentKind": "Workbook", + "displayName": "[parameters('workbook1-name')]", + "contentProductId": "[variables('_workbookcontentProductId1')]", + "id": "[variables('_workbookcontentProductId1')]", + "version": "[variables('workbookVersion1')]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('workbookTemplateSpecName2')]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "GSANetworkTraffic Workbook with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('workbookVersion2')]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Insights/workbooks", + "name": "[variables('workbookContentId2')]", + "location": "[parameters('workspace-location')]", + "kind": "shared", + "apiVersion": "2021-08-01", + "metadata": { + "description": "This workbook provides an overview of all traffic logs within your network, offering insights into data transfer, anomalies, and potential threats." + }, + "properties": { + "displayName": "[parameters('workbook2-name')]", + "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":1,\"content\":{\"json\":\"## Network Traffic Insights Workbook (Preview)\\n---\\nInformation in the dashboard is based on log data\\n\\n\\n\"},\"name\":\"text - 2\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"value::all\"],\"parameters\":[{\"id\":\"ff8b2a55-1849-4848-acf8-eab5452e9f10\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"LogAnalyticWorkspace\",\"label\":\"Log Analytic Workspace\",\"type\":5,\"description\":\"The Log Analytic Workspace In Which To Execute The Queries\",\"isRequired\":true,\"query\":\"resources\\r\\n| where type == \\\"microsoft.operationalinsights/workspaces\\\"\\r\\n| project id\",\"crossComponentResources\":[\"value::all\"],\"typeSettings\":{\"resourceTypeFilter\":{\"microsoft.operationalinsights/workspaces\":true},\"showDefault\":false},\"timeContext\":{\"durationMs\":2592000000},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},{\"id\":\"f15f34d8-8e2d-4c39-8dee-be2f979c86a8\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TimeRange\",\"label\":\"Time Range\",\"type\":4,\"isRequired\":true,\"typeSettings\":{\"selectableValues\":[{\"durationMs\":300000},{\"durationMs\":900000},{\"durationMs\":1800000},{\"durationMs\":3600000},{\"durationMs\":14400000},{\"durationMs\":43200000},{\"durationMs\":86400000},{\"durationMs\":172800000},{\"durationMs\":259200000},{\"durationMs\":604800000},{\"durationMs\":1209600000},{\"durationMs\":2419200000},{\"durationMs\":2592000000}],\"allowCustom\":true},\"timeContext\":{\"durationMs\":86400000},\"value\":{\"durationMs\":604800000}},{\"id\":\"8bab511b-53b3-4220-9d1c-372345b06728\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Users\",\"type\":2,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"NetworkAccessTraffic\\r\\n| summarize Count = count() by UserPrincipalName\\r\\n| order by Count desc, UserPrincipalName asc\\r\\n| project Value = UserPrincipalName, Label = strcat(UserPrincipalName, ' - ', Count, ' Logs'), Selected = false\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"typeSettings\":{\"limitSelectTo\":20,\"additionalResourceOptions\":[\"value::all\"],\"selectAllValue\":\"*\",\"showDefault\":false},\"timeContext\":{\"durationMs\":0},\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":[\"value::all\"]},{\"id\":\"527af4d2-3089-4aa4-9fbb-48ec697db20d\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"WebCategories\",\"label\":\"Web Categories\",\"type\":2,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"NetworkAccessTraffic\\r\\n| extend firstCategory = tostring(split(DestinationWebCategories, ',')[0]) // Split and get the first category\\r\\n| summarize Count = count() by firstCategory\\r\\n| order by Count desc, firstCategory asc\\r\\n| project Value = firstCategory, Label = strcat(firstCategory, ' - ', Count, ' Logs')\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"typeSettings\":{\"additionalResourceOptions\":[\"value::all\"],\"selectAllValue\":\"*\",\"showDefault\":false},\"timeContext\":{\"durationMs\":604800000},\"timeContextFromParameter\":\"TimeRange\",\"defaultValue\":\"value::all\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"value\":[\"value::all\"]}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 15\"},{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"tabs\",\"links\":[{\"id\":\"2b2cd1be-9d25-412c-8444-f005c4789b55\",\"cellValue\":\"tabSel\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Overview\",\"subTarget\":\"Overview\",\"style\":\"link\"},{\"id\":\"cc3e67f2-f20f-4430-8dee-d0773b90d9ce\",\"cellValue\":\"tabSel\",\"linkTarget\":\"parameter\",\"linkLabel\":\"All Traffic\",\"subTarget\":\"AllTraffic\",\"style\":\"link\"},{\"id\":\"5ae54b5a-ac7b-4b7a-a1e1-1e574625caa3\",\"cellValue\":\"tabSel\",\"linkTarget\":\"parameter\",\"linkLabel\":\"URL Lookup\",\"subTarget\":\"URLLookup\",\"style\":\"link\"},{\"id\":\"68c566f8-957e-4a3f-8b66-730fc24135fb\",\"cellValue\":\"tabSel\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Security Insights\",\"subTarget\":\"Security\",\"style\":\"link\"}]},\"name\":\"links - 7\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let NetworkAccessTrafficData = NetworkAccessTraffic\\r\\n| where SourceIp != \\\"\\\" and isnotempty(SourceIp)\\r\\n| summarize arg_max(TimeGenerated, *) by SourceIp, TenantId;\\r\\n\\r\\nlet SigninLogsData = SigninLogs\\r\\n| where IPAddress != \\\"\\\" and isnotempty(IPAddress)\\r\\n| summarize arg_max(TimeGenerated, *) by IPAddress, TenantId, UserId, CorrelationId;\\r\\n\\r\\nSigninLogsData\\r\\n| join kind=leftanti (\\r\\n NetworkAccessTrafficData\\r\\n | where SourceIp != \\\"\\\" and isnotempty(SourceIp)\\r\\n) on $left.IPAddress == $right.SourceIp and $left.TenantId == $right.TenantId\\r\\n| project TimeGenerated, IPAddress, UserId, UserPrincipalName, AppDisplayName, DeviceDetail.deviceId\\r\\n\",\"size\":0,\"title\":\"Sign-ins Outside Global Secure Access\",\"timeContextFromParameter\":\"TimeRange\",\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"rowLimit\":1000,\"filter\":true}},\"name\":\"query - 0\"}]},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"Security\"},\"name\":\"group - 10\"},{\"type\":1,\"content\":{\"json\":\"šŸ’” Type the URL for detailed analysis\"},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"URLLookup\"},\"name\":\"text - 10\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"ec8c38c8-3064-4921-8dcd-a69d3895599b\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"URL\",\"type\":1,\"typeSettings\":{\"isSearchBox\":true},\"timeContext\":{\"durationMs\":86400000}}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"URLLookup\"},\"name\":\"parameters - 9\"},{\"type\":1,\"content\":{\"json\":\"# Web Categories\"},\"name\":\"text - 11\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let CategoryData = NetworkAccessTraffic \\r\\n| where DestinationFqdn contains \\\"{URL}\\\" // Filters logs based on the provided URL parameter\\r\\n| extend CategoriesList = split(DestinationWebCategories, \\\",\\\") // Split categories into a list\\r\\n| mv-expand Category = CategoriesList // Expand the list into individual rows\\r\\n| extend Category = trim_start(\\\" \\\", trim_end(\\\" \\\", tostring(Category))) // Trim leading and trailing spaces\\r\\n| extend Category = iff(TrafficType == \\\"microsoft365\\\", \\\"Microsoft M365\\\", Category) // Set category to \\\"Microsoft M365\\\" if TrafficType is \\\"microsoft365\\\"\\r\\n| summarize UniqueCategories = make_set(Category); // Create a set of unique categories\\r\\n\\r\\nCategoryData\\r\\n| extend \\r\\n PrimaryCategory = iff(array_length(UniqueCategories) > 0, tostring(UniqueCategories[0]), \\\"None\\\")\\r\\n| project \\r\\n col1 = PrimaryCategory,\\r\\n snapshot = \\\"Primary Category\\\"\\r\\n\\r\\n| union (\\r\\n CategoryData\\r\\n | extend \\r\\n SecondaryCategory = iff(array_length(UniqueCategories) > 1, tostring(UniqueCategories[1]), \\\"None\\\")\\r\\n | project \\r\\n col1 = SecondaryCategory,\\r\\n snapshot = \\\"Secondary Category\\\"\\r\\n)\\r\\n\\r\\n| order by snapshot asc\\r\\n\",\"size\":4,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"snapshot\"},\"leftContent\":{\"columnMatch\":\"col1\",\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"maximumFractionDigits\":2}}},\"showBorder\":true,\"size\":\"auto\"}},\"name\":\"query - 17\"},{\"type\":1,\"content\":{\"json\":\"# Traffic Access Details\"},\"name\":\"text - 15\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let NetworkData = NetworkAccessTraffic\\r\\n| where DestinationFqdn contains \\\"{URL}\\\"; // Filters logs based on the provided URL parameter\\r\\n\\r\\nNetworkData\\r\\n| summarize \\r\\n UniqueUsers = dcount(UserPrincipalName),\\r\\n UniqueDevices = dcount(DeviceId), \\r\\n TotalAllow = countif(Action == \\\"Allow\\\"),\\r\\n TotalBlock = countif(Action == \\\"Block\\\")\\r\\n| project \\r\\n col1 = UniqueUsers, \\r\\n snapshot = \\\"Unique Users\\\"\\r\\n| union (NetworkData\\r\\n | summarize UniqueDevices = dcount(DeviceId)\\r\\n | project col1 = UniqueDevices, snapshot = \\\"Unique Devices\\\")\\r\\n| union (NetworkData\\r\\n | summarize TotalAllow = countif(Action == \\\"Allow\\\")\\r\\n | project col1 = TotalAllow, snapshot = \\\"Total Allow\\\")\\r\\n| union (NetworkData\\r\\n | summarize TotalBlock = countif(Action == \\\"Block\\\")\\r\\n | project col1 = TotalBlock, snapshot = \\\"Total Block\\\")\\r\\n| order by snapshot asc\",\"size\":4,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"snapshot\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"col1\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"maximumFractionDigits\":2,\"maximumSignificantDigits\":3}}},\"showBorder\":true}},\"name\":\"query - 14\"},{\"type\":1,\"content\":{\"json\":\"# Bandwidth Usage\"},\"name\":\"text - 16\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let NetworkData = NetworkAccessTraffic\\r\\n| where DestinationFqdn contains \\\"{URL}\\\"; // Filters logs based on the provided URL parameter\\r\\n\\r\\nNetworkData\\r\\n| summarize \\r\\n TotalSent = sum(SentBytes),\\r\\n TotalReceived = sum(ReceivedBytes),\\r\\n TotalBandwidth = sum(SentBytes + ReceivedBytes)\\r\\n| extend \\r\\n TotalSentMB = round(TotalSent / 1024.0 / 1024.0, 2),\\r\\n TotalReceivedMB = round(TotalReceived / 1024.0 / 1024.0, 2),\\r\\n TotalBandwidthMB = round(TotalBandwidth / 1024.0 / 1024.0, 2)\\r\\n| project \\r\\n TotalSentMB,\\r\\n TotalReceivedMB,\\r\\n TotalBandwidthMB\\r\\n| extend dummy = 1\\r\\n| project-away dummy\\r\\n| mv-expand \\r\\n Column = pack_array(\\\"TotalSentMB\\\", \\\"TotalReceivedMB\\\", \\\"TotalBandwidthMB\\\"),\\r\\n Value = pack_array(TotalSentMB, TotalReceivedMB, TotalBandwidthMB)\\r\\n| extend \\r\\n snapshot = case(\\r\\n Column == \\\"TotalSentMB\\\", \\\"Total Sent (MB)\\\",\\r\\n Column == \\\"TotalReceivedMB\\\", \\\"Total Received (MB)\\\",\\r\\n Column == \\\"TotalBandwidthMB\\\", \\\"Total Bandwidth (MB)\\\",\\r\\n \\\"Unknown\\\"\\r\\n ),\\r\\n col1 = iff(Value < 0.01, 0.00, Value)\\r\\n| project-away Column, Value\\r\\n| order by snapshot asc\",\"size\":4,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"snapshot\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"col1\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"maximumFractionDigits\":2,\"maximumSignificantDigits\":3}}},\"showBorder\":true}},\"name\":\"query - 17\"},{\"type\":1,\"content\":{\"json\":\"# Traffic Logs (filtered)\"},\"name\":\"text - 12\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| where DestinationFqdn contains \\\"{URL}\\\"\\r\\n| project \\r\\n Timestamp = TimeGenerated,\\r\\n User = UserPrincipalName,\\r\\n [\\\"Source IP\\\"] = SourceIp,\\r\\n [\\\"Destination IP\\\"] = DestinationIp,\\r\\n [\\\"Destination Port\\\"] = DestinationPort,\\r\\n [\\\"Destination FQDN\\\"] = DestinationFqdn,\\r\\n Action = case(\\r\\n tolower(Action) == \\\"allow\\\", \\\"šŸŸ¢ Allow\\\", \\r\\n tolower(Action) == \\\"block\\\", \\\"šŸ”“ Block\\\", \\r\\n tolower(Action) // This returns the action in lowercase if it doesn't match \\\"allow\\\" or \\\"block\\\"\\r\\n ),\\r\\n [\\\"Policy Name\\\"] = PolicyName,\\r\\n [\\\"Policy Rule ID\\\"] = PolicyRuleId,\\r\\n [\\\"Received Bytes\\\"] = ReceivedBytes,\\r\\n [\\\"Sent Bytes\\\"] = SentBytes,\\r\\n [\\\"Device OS\\\"] = DeviceOperatingSystem \\r\\n| order by Timestamp desc\\r\\n\",\"size\":0,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"]},\"name\":\"query - 13\"}]},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"URLLookup\"},\"name\":\"group - URL Lookup\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| project \\r\\n Timestamp = TimeGenerated,\\r\\n User = UserPrincipalName,\\r\\n [\\\"Source IP\\\"] = SourceIp,\\r\\n [\\\"Destination IP\\\"] = DestinationIp,\\r\\n [\\\"Destination Port\\\"] = DestinationPort,\\r\\n [\\\"Destination FQDN\\\"] = DestinationFqdn,\\r\\n Action = case(\\r\\n tolower(Action) == \\\"allow\\\", \\\"šŸŸ¢ Allow\\\", \\r\\n tolower(Action) == \\\"block\\\", \\\"šŸ”“ Block\\\", \\r\\n tolower(Action) // This returns the action in lowercase if it doesn't match \\\"allow\\\" or \\\"block\\\"\\r\\n ),\\r\\n [\\\"Policy Name\\\"] = PolicyName,\\r\\n [\\\"Web Category\\\"] = DestinationWebCategories,\\r\\n [\\\"Transport Protocol\\\"] = TransportProtocol,\\r\\n [\\\"Traffic Type\\\"] = TrafficType,\\r\\n [\\\"Received Bytes\\\"] = ReceivedBytes,\\r\\n [\\\"Sent Bytes\\\"] = SentBytes,\\r\\n [\\\"Device OS\\\"] = DeviceOperatingSystem,\\r\\n [\\\"Policy Rule ID\\\"] = PolicyRuleId\\r\\n| order by Timestamp desc\\r\\n\",\"size\":3,\"title\":\"Traffic Logs\",\"timeContextFromParameter\":\"TimeRange\",\"showRefreshButton\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"rowLimit\":1000,\"filter\":true}},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"AllTraffic\"},\"name\":\"query - 6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"// Unique Users\\nNetworkAccessTraffic\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Extend each row with geolocation info\\n| project SourceIp, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\\n| summarize UniqueUsers=dcount(Country)\\n| extend snapshot = \\\"Total Locations\\\"\\n| project col1 = UniqueUsers, snapshot\\n\\n// Union with Unique Devices\\n| union (\\n NetworkAccessTraffic\\n | where UserPrincipalName in ({Users}) or '*' in ({Users})\\n | extend BytesInGB = todouble(SentBytes + ReceivedBytes) / (1024 * 1024 * 1024) // Convert bytes to gigabytes\\n | summarize TotalBytesGB = sum(BytesInGB)\\n | extend snapshot = \\\"Total Bytes (GB)\\\"\\n | project col1 = tolong(TotalBytesGB), snapshot\\n)\\n\\n// Union with Total Internet Access\\n| union (\\n NetworkAccessTraffic\\n | where UserPrincipalName in ({Users}) or '*' in ({Users})\\n | summarize TotalTransactions = count()\\n | extend snapshot = \\\"Total Transactions\\\"\\n | project col1 = TotalTransactions, snapshot\\n)\\n\\n// Union with Total Private Access\\n// Order by Snapshot for consistent tile ordering on dashboard\\n| order by snapshot\",\"size\":4,\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"snapshot\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"col1\",\"formatter\":12,\"formatOptions\":{\"palette\":\"auto\"}},\"showBorder\":true,\"size\":\"auto\"},\"mapSettings\":{\"locInfo\":\"LatLong\",\"sizeSettings\":\"ExistingClients\",\"sizeAggregation\":\"Sum\",\"legendMetric\":\"ExistingClients\",\"legendAggregation\":\"Sum\",\"itemColorSettings\":{\"type\":\"heatmap\",\"colorAggregation\":\"Sum\",\"nodeColorField\":\"ExistingClients\",\"heatmapPalette\":\"greenRed\"}},\"textSettings\":{\"style\":\"bignumber\"}},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 2\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| extend BytesIn = todouble(SentBytes + ReceivedBytes) / (1024 * 1024) // Convert bytes to Mbytes\\r\\n| summarize TotalBytesMB = sum(BytesIn) by bin(TimeGenerated, 1h), TrafficType\\r\\n| order by bin(TimeGenerated, 1h) asc, TrafficType asc\\r\\n| project TimeGenerated, TrafficType, TotalBytesMB\\r\\n\",\"size\":2,\"title\":\"Usage Over Time (MB)\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"barchart\"},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"query - 0\"}]},\"name\":\"group - 5\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Extend each row with geolocation info\\r\\n| project TimeGenerated, SourceIp, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\\r\\n| where Country != \\\"\\\"\\r\\n| summarize Count = count() by City, State, Country\\r\\n\\r\\n\",\"size\":3,\"title\":\"Locations\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"map\",\"mapSettings\":{\"locInfo\":\"CountryRegion\",\"locInfoColumn\":\"Country\",\"latitude\":\"Latitude\",\"longitude\":\"Longitude\",\"sizeSettings\":\"Count\",\"sizeAggregation\":\"Sum\",\"labelSettings\":\"Country\",\"legendMetric\":\"Country\",\"legendAggregation\":\"Count\",\"itemColorSettings\":{\"nodeColorField\":\"Count\",\"colorAggregation\":\"Sum\",\"type\":\"heatmap\",\"heatmapPalette\":\"turquoise\"}}},\"name\":\"query - 0\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| where tolower(Action) == \\\"allow\\\" and DestinationWebCategories != '' // Filter for allowed traffic\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| extend firstCategory = tostring(split(DestinationWebCategories, ',')[0]) // Split and get the first category\\r\\n| summarize Count = count() by firstCategory\\r\\n| top 10 by Count\\r\\n\",\"size\":2,\"title\":\"Top Allowed Web Categories\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"piechart\"},\"customWidth\":\"50\",\"name\":\"query - 7\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| where tolower(Action) == \\\"block\\\" and DestinationWebCategories != '' // Filter for blocked traffic\\r\\n| extend firstCategory = tostring(split(DestinationWebCategories, ',')[0]) // Split and get the first category\\r\\n| summarize Count = count() by firstCategory\\r\\n| top 10 by Count\\r\\n\",\"size\":3,\"title\":\"Top Blocked Web Categories\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"piechart\"},\"customWidth\":\"50\",\"name\":\"query - 6\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic \\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| where tolower(Action) == \\\"block\\\" and DestinationFqdn != '' // Filter for blocked traffic with non-empty Destination FQDN\\r\\n| summarize Count = count() by [\\\"Destination FQDN\\\"] = DestinationFqdn, [\\\"Destination Web Categories\\\"] = DestinationWebCategories, [\\\"Policy Name\\\"] = PolicyName\\r\\n| order by Count\\r\\n\",\"size\":0,\"title\":\"Top Blocked Destinations\",\"timeContextFromParameter\":\"TimeRange\",\"showRefreshButton\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Count\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\"}}],\"rowLimit\":1000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"$gen_bar_Count_3\",\"sortOrder\":2}]},\"sortBy\":[{\"itemKey\":\"$gen_bar_Count_3\",\"sortOrder\":2}]},\"customWidth\":\"50\",\"name\":\"query - 5\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\\r\\n| where SentBytes > 0\\r\\n| where tolower(Action) != \\\"block\\\"\\r\\n| summarize \\r\\n Count = count(), \\r\\n [\\\"Sent Bytes\\\"] = sum(SentBytes), \\r\\n [\\\"Received Bytes\\\"] = sum(ReceivedBytes), \\r\\n [\\\"Total Bytes\\\"] = sum(ReceivedBytes + SentBytes) \\r\\n by [\\\"Destination FQDN\\\"] = DestinationFqdn\\r\\n| order by Count desc\\r\\n\",\"size\":0,\"title\":\"Top Allowed Destinations\",\"timeContextFromParameter\":\"TimeRange\",\"showRefreshButton\":true,\"showExportToExcel\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"Count\",\"formatter\":4,\"formatOptions\":{\"palette\":\"magenta\"}},{\"columnMatch\":\"Recived\",\"formatter\":4,\"formatOptions\":{\"palette\":\"turquoise\"}},{\"columnMatch\":\"Total\",\"formatter\":4,\"formatOptions\":{\"palette\":\"pink\"}},{\"columnMatch\":\"Sent\",\"formatter\":4,\"formatOptions\":{\"palette\":\"blue\"}}],\"rowLimit\":1000,\"filter\":true,\"sortBy\":[{\"itemKey\":\"$gen_bar_Count_1\",\"sortOrder\":2}]},\"sortBy\":[{\"itemKey\":\"$gen_bar_Count_1\",\"sortOrder\":2}]},\"customWidth\":\"50\",\"name\":\"query - 1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"NetworkAccessTraffic\\r\\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\\r\\n| where TransportProtocol != ''\\r\\n| summarize Count = count() by toupper(TransportProtocol)\\r\\n| top 10 by Count\\r\\n\",\"size\":2,\"title\":\"Protocol Distribution\",\"timeContextFromParameter\":\"TimeRange\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticWorkspace}\"],\"visualization\":\"piechart\"},\"customWidth\":\"50\",\"name\":\"query - 3\"}]},\"conditionalVisibility\":{\"parameterName\":\"tabSel\",\"comparison\":\"isEqualTo\",\"value\":\"Overview\"},\"name\":\"group - 4\"}],\"fallbackResourceIds\":[\"Global Secure Access\"],\"fromTemplateId\":\"GSA Network Traffic Insights\",\"$schema\":\"https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json\"}\r\n", + "version": "1.0", + "sourceId": "[variables('workspaceResourceId')]", + "category": "sentinel" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('Workbook-', last(split(variables('workbookId2'),'/'))))]", + "properties": { + "description": "@{workbookKey=GSANetworkTraffic; logoFileName=gsa.svg; description=This workbook provides an overview of all traffic logs within your network, offering insights into data transfer, anomalies, and potential threats.; dataTypesDependencies=System.Object[]; dataConnectorsDependencies=System.Object[]; previewImagesFileNames=System.Object[]; version=1.0.1; title=Microsoft Global Secure Access Traffic Logs; templateRelativePath=GSANetworkTraffic.json; subtitle=; provider=Microsoft}.description", + "parentId": "[variables('workbookId2')]", + "contentId": "[variables('_workbookContentId2')]", + "kind": "Workbook", + "version": "[variables('workbookVersion2')]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + }, + "dependencies": { + "operator": "AND", + "criteria": [ + { + "contentId": "AzureActiveDirectory", + "kind": "DataConnector" + } + ] + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('_workbookContentId2')]", + "contentKind": "Workbook", + "displayName": "[parameters('workbook2-name')]", + "contentProductId": "[variables('_workbookcontentProductId2')]", + "id": "[variables('_workbookcontentProductId2')]", + "version": "[variables('workbookVersion2')]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject1').analyticRuleTemplateSpecName1]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "Identity - AfterHoursActivity_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject1').analyticRuleVersion1]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject1')._analyticRulecontentId1]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "This query identifies connections that occur outside of the defined operational hours. It helps in monitoring and flagging any unusual activity that may occur during non-business hours, indicating potential security concerns or policy violations.", + "displayName": "Detect Connections Outside Operational Hours", + "enabled": false, + "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet operational_start_hour = 8; // Start of operational hours (8 AM)\nlet operational_end_hour = 18; // End of operational hours (6 PM)\nNetworkAccessTraffic\n| where TimeGenerated between(starttime .. endtime)\n| extend HourOfDay = datetime_part('hour', TimeGenerated)\n| where HourOfDay < operational_start_hour or HourOfDay >= operational_end_hour\n| project TimeGenerated, UserPrincipalName, SourceIp, DestinationIp, DestinationPort, Action, DeviceId, DeviceOperatingSystem, ConnectionId\n| extend IPCustomEntity = SourceIp, AccountCustomEntity = UserPrincipalName\n", + "queryFrequency": "PT1H", + "queryPeriod": "PT24H", + "severity": "High", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "EnrichedMicrosoft365AuditLogs" + ], + "connectorId": "AzureActiveDirectory" + } + ], + "tactics": [ + "InitialAccess" + ], + "techniques": [ + "T1078", + "T1133" + ], + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "AccountCustomEntity", + "identifier": "Name" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "columnName": "IPCustomEntity", + "identifier": "Address" + } + ] + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject1').analyticRuleId1,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 1", + "parentId": "[variables('analyticRuleObject1').analyticRuleId1]", + "contentId": "[variables('analyticRuleObject1')._analyticRulecontentId1]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject1').analyticRuleVersion1]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject1')._analyticRulecontentId1]", + "contentKind": "AnalyticsRule", + "displayName": "Detect Connections Outside Operational Hours", + "contentProductId": "[variables('analyticRuleObject1')._analyticRulecontentProductId1]", + "id": "[variables('analyticRuleObject1')._analyticRulecontentProductId1]", + "version": "[variables('analyticRuleObject1').analyticRuleVersion1]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject2').analyticRuleTemplateSpecName2]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "Identity - SharedSessions_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject2').analyticRuleVersion2]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject2')._analyticRulecontentId2]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "This query identifies network sessions based on DeviceId and UserPrincipalName, then checks for changed IP addresses and overlapping session times.", + "displayName": "Detect IP Address Changes and Overlapping Sessions", + "enabled": false, + "query": "// Identify sessions\nlet sessions = \n NetworkAccessTraffic\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), SourceIps = make_set(SourceIp) by DeviceId, UserPrincipalName, SessionId\n | sort by StartTime asc;\n// Check for changed IP addresses and overlapping session times\nsessions\n | extend PreviousSourceIps = prev(SourceIps, 1)\n | extend PreviousEndTime = prev(EndTime, 1)\n | extend PreviousDeviceId = prev(DeviceId, 1)\n | extend PreviousUserPrincipalName = prev(UserPrincipalName, 1)\n | where DeviceId == PreviousDeviceId and UserPrincipalName == PreviousUserPrincipalName\n | where set_difference(SourceIps, PreviousSourceIps) != dynamic([]) // Check if the current and previous IP sets differ\n | where PreviousEndTime > StartTime // Check for overlapping session times\n | project DeviceId, UserPrincipalName, SourceIps, PreviousSourceIps, StartTime, EndTime, PreviousEndTime\n | extend IPCustomEntity = tostring(array_slice(SourceIps, 0, 1)[0]), PreviousIPCustomEntity = tostring(array_slice(PreviousSourceIps, 0, 1)[0]), AccountCustomEntity = UserPrincipalName\n", + "queryFrequency": "PT1H", + "queryPeriod": "PT24H", + "severity": "High", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "EnrichedMicrosoft365AuditLogs" + ], + "connectorId": "AzureActiveDirectory" + } + ], + "tactics": [ + "InitialAccess" + ], + "techniques": [ + "T1078", + "T1133" + ], + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "AccountCustomEntity", + "identifier": "Name" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "columnName": "IPCustomEntity", + "identifier": "Address" + } + ] + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject2').analyticRuleId2,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 2", + "parentId": "[variables('analyticRuleObject2').analyticRuleId2]", + "contentId": "[variables('analyticRuleObject2')._analyticRulecontentId2]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject2').analyticRuleVersion2]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject2')._analyticRulecontentId2]", + "contentKind": "AnalyticsRule", + "displayName": "Detect IP Address Changes and Overlapping Sessions", + "contentProductId": "[variables('analyticRuleObject2')._analyticRulecontentProductId2]", + "id": "[variables('analyticRuleObject2')._analyticRulecontentProductId2]", + "version": "[variables('analyticRuleObject2').analyticRuleVersion2]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject3').analyticRuleTemplateSpecName3]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "Office 365 - exchange_auditlogdisabled_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject3').analyticRuleVersion3]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject3')._analyticRulecontentId3]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "Identifies when the Exchange audit logging has been disabled, which may indicate an adversary attempt to evade detection or bypass other defenses.", + "displayName": "GSA Enriched Office 365 - Exchange AuditLog Disabled", + "enabled": false, + "query": "// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"Exchange\" \n | where UserType in~ (\"Admin\", \"DcAdmin\")\n // Only admin or global-admin can disable audit logging\n | where Operation =~ \"Set-AdminAuditLogConfig\"\n | extend ParsedParameters = parse_json(Parameters)\n | extend AdminAuditLogEnabledValue = tostring(ParsedParameters[3].Value)\n | where AdminAuditLogEnabledValue =~ \"False\"\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() \n by Operation, UserType, UserId, ClientIP, ResultStatus, Parameters, AdminAuditLogEnabledValue\n | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), \n iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[1]), UserId))\n | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '')\n | extend AccountNTDomain = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[0]), '');\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"Exchange\"\n | where UserType in~ (\"Admin\", \"DcAdmin\")\n | where Operation =~ \"Set-AdminAuditLogConfig\"\n | extend ParsedParameters = parse_json(AdditionalProperties.Parameters)\n | extend AdminAuditLogEnabledValue = tostring(ParsedParameters[3].Value)\n | where AdminAuditLogEnabledValue =~ \"False\"\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() \n by Operation, UserType, UserId, ClientIP = SourceIp, ResultStatus, Parameters = tostring(AdditionalProperties.Parameters), AdminAuditLogEnabledValue\n | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), \n iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[1]), UserId))\n | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '')\n | extend AccountNTDomain = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[0]), '');\n// Combine Office and Enriched Events and Deduplicate\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTimeUtc, *) by Operation, UserId, ClientIP;\n// Project Final Output\nCombinedEvents\n | project StartTimeUtc, EndTimeUtc, Operation, UserType, UserId, ClientIP, ResultStatus, Parameters, AdminAuditLogEnabledValue, AccountName, AccountUPNSuffix, AccountNTDomain\n", + "queryFrequency": "P1D", + "queryPeriod": "P1D", + "severity": "Medium", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "EnrichedMicrosoft365AuditLogs" + ], + "connectorId": "AzureActiveDirectory" + }, + { + "dataTypes": [ + "OfficeActivity (Exchange)" + ], + "connectorId": "Office365" + } + ], + "tactics": [ + "DefenseEvasion" + ], + "techniques": [ + "T1562" + ], + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "UserId", + "identifier": "FullName" + }, + { + "columnName": "AccountName", + "identifier": "Name" + }, + { + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "columnName": "ClientIP", + "identifier": "Address" + } + ] + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject3').analyticRuleId3,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 3", + "parentId": "[variables('analyticRuleObject3').analyticRuleId3]", + "contentId": "[variables('analyticRuleObject3')._analyticRulecontentId3]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject3').analyticRuleVersion3]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject3')._analyticRulecontentId3]", + "contentKind": "AnalyticsRule", + "displayName": "GSA Enriched Office 365 - Exchange AuditLog Disabled", + "contentProductId": "[variables('analyticRuleObject3')._analyticRulecontentProductId3]", + "id": "[variables('analyticRuleObject3')._analyticRulecontentProductId3]", + "version": "[variables('analyticRuleObject3').analyticRuleVersion3]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject4').analyticRuleTemplateSpecName4]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "Office 365 - External User added to Team and immediately uploads file_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject4').analyticRuleVersion4]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject4')._analyticRulecontentId4]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "This detection identifies when an external user is added to a Team or Teams chat and shares a file which is accessed by many users (>10) and the users is removed within short period of time. This might be an indicator of suspicious activity.", + "displayName": "GSA Enriched Office 365 - Accessed files shared by temporary external user", + "enabled": false, + "query": "let fileAccessThreshold = 10;\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberAdded\"\n | extend MemberAdded = tostring(parse_json(Members)[0].UPN)\n | where MemberAdded contains \"#EXT#\"\n | project TimeAdded = TimeGenerated, Operation, MemberAdded, UserWhoAdded = UserId, TeamName\n | join kind=inner (\n OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberRemoved\"\n | extend MemberAdded = tostring(parse_json(Members)[0].UPN)\n | where MemberAdded contains \"#EXT#\"\n | project TimeDeleted = TimeGenerated, Operation, MemberAdded, UserWhoDeleted = UserId, TeamName\n ) on MemberAdded\n | where TimeDeleted > TimeAdded\n | join kind=inner (\n OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | where Operation == \"FileUploaded\"\n | extend MemberAdded = UserId\n | join kind=inner (\n OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where Operation == \"FileAccessed\"\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | summarize FileAccessCount = count() by OfficeObjectId\n | where FileAccessCount > fileAccessThreshold\n ) on OfficeObjectId\n ) on MemberAdded\n | project TimeAdded, TimeDeleted, MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberAdded\"\n | extend MemberAdded = tostring(parse_json(tostring(AdditionalProperties)).Members[0].UPN)\n | where MemberAdded contains \"#EXT#\"\n | project TimeAdded = TimeGenerated, Operation, MemberAdded, UserWhoAdded = UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n | join kind=inner (\n EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberRemoved\"\n | extend MemberAdded = tostring(parse_json(tostring(AdditionalProperties)).Members[0].UPN)\n | where MemberAdded contains \"#EXT#\"\n | project TimeDeleted = TimeGenerated, Operation, MemberAdded, UserWhoDeleted = UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n ) on MemberAdded, TeamName\n | where TimeDeleted > TimeAdded\n | join kind=inner (\n EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation == \"FileUploaded\"\n | extend MemberAdded = UserId, SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl), TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | join kind=inner (\n EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation == \"FileAccessed\"\n | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl), TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | summarize FileAccessCount = count() by ObjectId, TeamName\n | where FileAccessCount > fileAccessThreshold\n ) on ObjectId, TeamName\n ) on MemberAdded, TeamName\n | project TimeAdded, TimeDeleted, MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName;\n// Combine Office and Enriched Events and Deduplicate\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeAdded, *) by MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName;\n// Project Final Output\nCombinedEvents\n | extend MemberAddedAccountName = tostring(split(MemberAdded, \"@\")[0]), MemberAddedAccountUPNSuffix = tostring(split(MemberAdded, \"@\")[1])\n | extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, \"@\")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, \"@\")[1])\n | extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, \"@\")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, \"@\")[1])\n | project TimeAdded, TimeDeleted, MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName, MemberAddedAccountName, MemberAddedAccountUPNSuffix, UserWhoAddedAccountName, UserWhoAddedAccountUPNSuffix, UserWhoDeletedAccountName, UserWhoDeletedAccountUPNSuffix\n", + "queryFrequency": "PT1H", + "queryPeriod": "PT1H", + "severity": "Low", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "EnrichedMicrosoft365AuditLogs" + ], + "connectorId": "AzureActiveDirectory" + }, + { + "dataTypes": [ + "OfficeActivity (Teams)" + ], + "connectorId": "Office365" + } + ], + "tactics": [ + "InitialAccess" + ], + "techniques": [ + "T1566" + ], + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "MemberAdded", + "identifier": "FullName" + }, + { + "columnName": "MemberAddedAccountName", + "identifier": "Name" + }, + { + "columnName": "MemberAddedAccountUPNSuffix", + "identifier": "UPNSuffix" + } + ] + }, + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "UserWhoAdded", + "identifier": "FullName" + }, + { + "columnName": "UserWhoAddedAccountName", + "identifier": "Name" + }, + { + "columnName": "UserWhoAddedAccountUPNSuffix", + "identifier": "UPNSuffix" + } + ] + }, + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "UserWhoDeleted", + "identifier": "FullName" + }, + { + "columnName": "UserWhoDeletedAccountName", + "identifier": "Name" + }, + { + "columnName": "UserWhoDeletedAccountUPNSuffix", + "identifier": "UPNSuffix" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "columnName": "ClientIP", + "identifier": "Address" + } + ] + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject4').analyticRuleId4,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 4", + "parentId": "[variables('analyticRuleObject4').analyticRuleId4]", + "contentId": "[variables('analyticRuleObject4')._analyticRulecontentId4]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject4').analyticRuleVersion4]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject4')._analyticRulecontentId4]", + "contentKind": "AnalyticsRule", + "displayName": "GSA Enriched Office 365 - Accessed files shared by temporary external user", + "contentProductId": "[variables('analyticRuleObject4')._analyticRulecontentProductId4]", + "id": "[variables('analyticRuleObject4')._analyticRulecontentProductId4]", + "version": "[variables('analyticRuleObject4').analyticRuleVersion4]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject5').analyticRuleTemplateSpecName5]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "Office 365 - ExternalUserAddedRemovedInTeams_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject5').analyticRuleVersion5]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject5')._analyticRulecontentId5]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "This detection flags the occurrences of external user accounts that are added to a Team and then removed within one hour.", + "displayName": "GSA Enriched Office 365 - External User Added and Removed in Short Timeframe", + "enabled": false, + "query": "let TeamsAddDelOffice = (Op:string){\n OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation == Op\n | where Members has (\"#EXT#\")\n | mv-expand Members\n | extend UPN = tostring(Members.UPN)\n | where UPN has (\"#EXT#\")\n | project TimeGenerated, Operation, UPN, UserId, TeamName, ClientIP\n};\nlet TeamsAddDelEnriched = (Op:string){\n EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"MicrosoftTeams\"\n | where Operation == Op\n | where tostring(AdditionalProperties.Members) has (\"#EXT#\")\n | mv-expand Members = parse_json(tostring(AdditionalProperties.Members))\n | extend UPN = tostring(Members.UPN)\n | where UPN has (\"#EXT#\")\n | project TimeGenerated, Operation, UPN, UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName), ClientIP = SourceIp\n};\nlet TeamsAddOffice = TeamsAddDelOffice(\"MemberAdded\")\n | project TimeAdded = TimeGenerated, Operation, MemberAdded = UPN, UserWhoAdded = UserId, TeamName, ClientIP;\nlet TeamsDelOffice = TeamsAddDelOffice(\"MemberRemoved\")\n | project TimeDeleted = TimeGenerated, Operation, MemberRemoved = UPN, UserWhoDeleted = UserId, TeamName, ClientIP;\nlet TeamsAddEnriched = TeamsAddDelEnriched(\"MemberAdded\")\n | project TimeAdded = TimeGenerated, Operation, MemberAdded = UPN, UserWhoAdded = UserId, TeamName, ClientIP;\nlet TeamsDelEnriched = TeamsAddDelEnriched(\"MemberRemoved\")\n | project TimeDeleted = TimeGenerated, Operation, MemberRemoved = UPN, UserWhoDeleted = UserId, TeamName, ClientIP;\nlet TeamsAdd = TeamsAddOffice\n | union TeamsAddEnriched\n | project TimeAdded, Operation, MemberAdded, UserWhoAdded, TeamName, ClientIP;\nlet TeamsDel = TeamsDelOffice\n | union TeamsDelEnriched\n | project TimeDeleted, Operation, MemberRemoved, UserWhoDeleted, TeamName, ClientIP;\nTeamsAdd\n| join kind=inner (TeamsDel) on $left.MemberAdded == $right.MemberRemoved\n| where TimeDeleted > TimeAdded\n| project TimeAdded, TimeDeleted, MemberAdded_Removed = MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName, ClientIP\n| extend MemberAdded_RemovedAccountName = tostring(split(MemberAdded_Removed, \"@\")[0]), MemberAdded_RemovedAccountUPNSuffix = tostring(split(MemberAdded_Removed, \"@\")[1])\n| extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, \"@\")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, \"@\")[1])\n| extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, \"@\")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, \"@\")[1])\n", + "queryFrequency": "PT1H", + "queryPeriod": "PT1H", + "severity": "Low", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "EnrichedMicrosoft365AuditLogs" + ], + "connectorId": "AzureActiveDirectory" + }, + { + "dataTypes": [ + "OfficeActivity (Teams)" + ], + "connectorId": "Office365" + } + ], + "tactics": [ + "Persistence" + ], + "techniques": [ + "T1136" + ], + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "MemberAdded_Removed", + "identifier": "FullName" + }, + { + "columnName": "MemberAdded_RemovedAccountName", + "identifier": "Name" + }, + { + "columnName": "MemberAdded_RemovedAccountUPNSuffix", + "identifier": "UPNSuffix" + } + ] + }, + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "UserWhoAdded", + "identifier": "FullName" + }, + { + "columnName": "UserWhoAddedAccountName", + "identifier": "Name" + }, + { + "columnName": "UserWhoAddedAccountUPNSuffix", + "identifier": "UPNSuffix" + } + ] + }, + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "UserWhoDeleted", + "identifier": "FullName" + }, + { + "columnName": "UserWhoDeletedAccountName", + "identifier": "Name" + }, + { + "columnName": "UserWhoDeletedAccountUPNSuffix", + "identifier": "UPNSuffix" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "columnName": "ClientIP", + "identifier": "Address" + } + ] + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject5').analyticRuleId5,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 5", + "parentId": "[variables('analyticRuleObject5').analyticRuleId5]", + "contentId": "[variables('analyticRuleObject5')._analyticRulecontentId5]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject5').analyticRuleVersion5]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject5')._analyticRulecontentId5]", + "contentKind": "AnalyticsRule", + "displayName": "GSA Enriched Office 365 - External User Added and Removed in Short Timeframe", + "contentProductId": "[variables('analyticRuleObject5')._analyticRulecontentProductId5]", + "id": "[variables('analyticRuleObject5')._analyticRulecontentProductId5]", + "version": "[variables('analyticRuleObject5').analyticRuleVersion5]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject6').analyticRuleTemplateSpecName6]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "Office 365 - Mail_redirect_via_ExO_transport_rule_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject6').analyticRuleVersion6]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject6')._analyticRulecontentId6]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "Identifies when an Exchange Online transport rule is configured to forward emails.\nThis could indicate an adversary mailbox configured to collect mail from multiple user accounts.", + "displayName": "GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule", + "enabled": false, + "query": "// OfficeActivity Query\nlet officeActivityQuery = OfficeActivity\n | where OfficeWorkload == \"Exchange\"\n | where Operation in~ (\"New-TransportRule\", \"Set-TransportRule\")\n | mv-apply DynamicParameters = todynamic(Parameters) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | extend RuleName = case(\n Operation =~ \"Set-TransportRule\", OfficeObjectId,\n Operation =~ \"New-TransportRule\", ParsedParameters.Name,\n \"Unknown\"\n )\n | mv-expand ExpandedParameters = todynamic(Parameters)\n | where ExpandedParameters.Name in~ (\"BlindCopyTo\", \"RedirectMessageTo\") and isnotempty(ExpandedParameters.Value)\n | extend RedirectTo = ExpandedParameters.Value\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIP)[0]\n | extend From = ParsedParameters.From\n | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters\n | extend AccountName = tostring(split(UserId, \"@\")[0]),\n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// EnrichedMicrosoft365AuditLogs Query\nlet enrichedLogsQuery = EnrichedMicrosoft365AuditLogs\n | where Workload == \"Exchange\"\n | where Operation in~ (\"New-TransportRule\", \"Set-TransportRule\")\n | extend AdditionalProps = parse_json(AdditionalProperties)\n | mv-apply DynamicParameters = todynamic(AdditionalProps.Parameters) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | extend RuleName = case(\n Operation =~ \"Set-TransportRule\", ObjectId,\n Operation =~ \"New-TransportRule\", ParsedParameters.Name,\n \"Unknown\"\n )\n | mv-expand ExpandedParameters = todynamic(AdditionalProps.Parameters)\n | where ExpandedParameters.Name in~ (\"BlindCopyTo\", \"RedirectMessageTo\") and isnotempty(ExpandedParameters.Value)\n | extend RedirectTo = ExpandedParameters.Value\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIp)[0]\n | extend From = ParsedParameters.From\n | extend UserAgent = tostring(AdditionalProps.UserAgent)\n | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters = tostring(AdditionalProps.Parameters), UserAgent\n | extend AccountName = tostring(split(UserId, \"@\")[0]),\n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// Combine both queries\nunion isfuzzy=true officeActivityQuery, enrichedLogsQuery\n| summarize arg_min(TimeGenerated, *) by RuleName, RedirectTo\n| project TimeGenerated, RedirectTo, IPAddress, Port, UserId, From, Operation, RuleName, Parameters, AccountName, AccountUPNSuffix\n| order by TimeGenerated desc;\n", + "queryFrequency": "PT1H", + "queryPeriod": "PT1H", + "severity": "Medium", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "OfficeActivity (Exchange)" + ], + "connectorId": "Office365" + }, + { + "dataTypes": [ + "EnrichedMicrosoft365AuditLogs" + ], + "connectorId": "AzureActiveDirectory" + } + ], + "tactics": [ + "Collection", + "Exfiltration" + ], + "techniques": [ + "T1114", + "T1020" + ], + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "UserId", + "identifier": "FullName" + }, + { + "columnName": "AccountName", + "identifier": "Name" + }, + { + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "columnName": "IPAddress", + "identifier": "Address" + } + ] + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject6').analyticRuleId6,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 6", + "parentId": "[variables('analyticRuleObject6').analyticRuleId6]", + "contentId": "[variables('analyticRuleObject6')._analyticRulecontentId6]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject6').analyticRuleVersion6]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject6')._analyticRulecontentId6]", + "contentKind": "AnalyticsRule", + "displayName": "GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule", + "contentProductId": "[variables('analyticRuleObject6')._analyticRulecontentProductId6]", + "id": "[variables('analyticRuleObject6')._analyticRulecontentProductId6]", + "version": "[variables('analyticRuleObject6').analyticRuleVersion6]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject7').analyticRuleTemplateSpecName7]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "Office 365 - Malicious_Inbox_Rule_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject7').analyticRuleVersion7]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject7')._analyticRulecontentId7]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "Often times after the initial compromise the attackers create inbox rules to delete emails that contain certain keywords.\nThis is done so as to limit ability to warn compromised users that they've been compromised. Below is a sample query that tries to detect this.\nReference: https://www.reddit.com/r/sysadmin/comments/7kyp0a/recent_phishing_attempts_my_experience_and_what/", + "displayName": "GSA Enriched Office 365 - Malicious Inbox Rule", + "enabled": false, + "query": "let Keywords = dynamic([\"helpdesk\", \"alert\", \"suspicious\", \"fake\", \"malicious\", \"phishing\", \"spam\", \"do not click\", \"do not open\", \"hijacked\", \"Fatal\"]);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"Exchange\" \n | where Operation =~ \"New-InboxRule\" and (ResultStatus =~ \"True\" or ResultStatus =~ \"Succeeded\")\n | where Parameters has \"Deleted Items\" or Parameters has \"Junk Email\" or Parameters has \"DeleteMessage\"\n | extend Events = todynamic(Parameters)\n | parse Events with * \"SubjectContainsWords\" SubjectContainsWords '}'*\n | parse Events with * \"BodyContainsWords\" BodyContainsWords '}'*\n | parse Events with * \"SubjectOrBodyContainsWords\" SubjectOrBodyContainsWords '}'*\n | where SubjectContainsWords has_any (Keywords)\n or BodyContainsWords has_any (Keywords)\n or SubjectOrBodyContainsWords has_any (Keywords)\n | extend ClientIPAddress = case(\n ClientIP has \".\", tostring(split(ClientIP, \":\")[0]),\n ClientIP has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIP, \"]\")[0]))),\n ClientIP\n )\n | extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords))\n | extend RuleDetail = case(\n OfficeObjectId contains '/', tostring(split(OfficeObjectId, '/')[-1]),\n OfficeObjectId contains '\\\\', tostring(split(OfficeObjectId, '\\\\')[-1]),\n \"Unknown\"\n )\n | summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Operation, UserId, ClientIPAddress, ResultStatus, Keyword, OriginatingServer, OfficeObjectId, RuleDetail\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend OriginatingServerName = tostring(split(OriginatingServer, \" \")[0]);\n\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"Exchange\"\n | where Operation =~ \"New-InboxRule\" and (ResultStatus =~ \"True\" or ResultStatus =~ \"Succeeded\")\n | where tostring(parse_json(tostring(AdditionalProperties)).Parameters) has \"Deleted Items\" \n or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has \"Junk Email\" \n or tostring(parse_json(tostring(AdditionalProperties)).Parameters) has \"DeleteMessage\"\n | extend Events = parse_json(tostring(AdditionalProperties)).Parameters\n | extend SubjectContainsWords = tostring(Events.SubjectContainsWords), \n BodyContainsWords = tostring(Events.BodyContainsWords), \n SubjectOrBodyContainsWords = tostring(Events.SubjectOrBodyContainsWords)\n | where SubjectContainsWords has_any (Keywords) \n or BodyContainsWords has_any (Keywords) \n or SubjectOrBodyContainsWords has_any (Keywords)\n | extend ClientIPAddress = case(\n ClientIp has \".\", tostring(split(ClientIp, \":\")[0]),\n ClientIp has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIp, \"]\")[0]))),\n ClientIp\n )\n | extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords))\n | extend RuleDetail = case(\n ObjectId contains '/', tostring(split(ObjectId, '/')[-1]),\n ObjectId contains '\\\\', tostring(split(ObjectId, '\\\\')[-1]),\n \"Unknown\"\n )\n | summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Operation, UserId, ClientIPAddress, ResultStatus, Keyword, ObjectId, RuleDetail\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Combine and Deduplicate\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTimeUtc, *) by Operation, UserId, ClientIPAddress;\n\n// Final Output\nCombinedEvents\n | project StartTimeUtc, EndTimeUtc, Operation, UserId, ClientIPAddress, ResultStatus, Keyword, RuleDetail, AccountName, AccountUPNSuffix;\n", + "queryFrequency": "P1D", + "queryPeriod": "P1D", + "severity": "Medium", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "EnrichedMicrosoft365AuditLogs" + ], + "connectorId": "AzureActiveDirectory" + }, + { + "dataTypes": [ + "OfficeActivity (Exchange)" + ], + "connectorId": "Office365" + } + ], + "tactics": [ + "Persistence", + "DefenseEvasion" + ], + "techniques": [ + "T1098", + "T1078" + ], + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "UserId", + "identifier": "FullName" + }, + { + "columnName": "AccountName", + "identifier": "Name" + }, + { + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "columnName": "ClientIPAddress", + "identifier": "Address" + } + ] + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject7').analyticRuleId7,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 7", + "parentId": "[variables('analyticRuleObject7').analyticRuleId7]", + "contentId": "[variables('analyticRuleObject7')._analyticRulecontentId7]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject7').analyticRuleVersion7]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject7')._analyticRulecontentId7]", + "contentKind": "AnalyticsRule", + "displayName": "GSA Enriched Office 365 - Malicious Inbox Rule", + "contentProductId": "[variables('analyticRuleObject7')._analyticRulecontentProductId7]", + "id": "[variables('analyticRuleObject7')._analyticRulecontentProductId7]", + "version": "[variables('analyticRuleObject7').analyticRuleVersion7]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject8').analyticRuleTemplateSpecName8]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "Office 365 - MultipleTeamsDeletes_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject8').analyticRuleVersion8]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject8')._analyticRulecontentId8]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "This detection flags the occurrences of deleting multiple teams within a day.\nThis data is a part of Office 365 Connector in Microsoft Sentinel.", + "displayName": "GSA Enriched Office 365 - Multiple Teams deleted by a single user", + "enabled": false, + "query": "// Set the maximum number of deleted teams to flag suspicious activity\nlet max_delete_count = 3;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload =~ \"MicrosoftTeams\"\n | where Operation =~ \"TeamDeleted\"\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DeletedTeams = make_set(tostring(AdditionalProperties.TeamName), 1000) by UserId\n | where array_length(DeletedTeams) > max_delete_count\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"TeamDeleted\"\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DeletedTeams = make_set(TeamName, 1000) by UserId\n | where array_length(DeletedTeams) > max_delete_count\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// Combine and Deduplicate Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by UserId;\n// Final Output\nCombinedEvents\n | project StartTime, EndTime, DeletedTeams, UserId, AccountName, AccountUPNSuffix\n", + "queryFrequency": "P1D", + "queryPeriod": "P1D", + "severity": "Low", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "EnrichedMicrosoft365AuditLogs" + ], + "connectorId": "AzureActiveDirectory" + }, + { + "dataTypes": [ + "OfficeActivity (Teams)" + ], + "connectorId": "Office365" + } + ], + "tactics": [ + "Impact" + ], + "techniques": [ + "T1485", + "T1489" + ], + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "UserId", + "identifier": "FullName" + }, + { + "columnName": "AccountName", + "identifier": "Name" + }, + { + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" + } + ] + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject8').analyticRuleId8,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 8", + "parentId": "[variables('analyticRuleObject8').analyticRuleId8]", + "contentId": "[variables('analyticRuleObject8')._analyticRulecontentId8]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject8').analyticRuleVersion8]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject8')._analyticRulecontentId8]", + "contentKind": "AnalyticsRule", + "displayName": "GSA Enriched Office 365 - Multiple Teams deleted by a single user", + "contentProductId": "[variables('analyticRuleObject8')._analyticRulecontentProductId8]", + "id": "[variables('analyticRuleObject8')._analyticRulecontentProductId8]", + "version": "[variables('analyticRuleObject8').analyticRuleVersion8]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject9').analyticRuleTemplateSpecName9]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "Office 365 - Office_MailForwarding_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject9').analyticRuleVersion9]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject9')._analyticRulecontentId9]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "Identifies when multiple (more than one) users' mailboxes are configured to forward to the same destination. \nThis could be an attacker-controlled destination mailbox configured to collect mail from multiple compromised user accounts.", + "displayName": "GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination", + "enabled": false, + "query": "// Set query parameters\nlet queryfrequency = 1d;\nlet queryperiod = 7d;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated > ago(queryperiod)\n | where Workload =~ \"Exchange\"\n // Uncomment or adjust the following line based on actual field usage\n // | where Operation in (\"Set-Mailbox\", \"New-InboxRule\", \"Set-InboxRule\")\n | where tostring(AdditionalProperties.Parameters) has_any (\"ForwardTo\", \"RedirectTo\", \"ForwardingSmtpAddress\")\n | mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source')\n | extend DestinationMailAddress = tolower(case(\n isnotempty(column_ifexists(\"ForwardTo\", \"\")), column_ifexists(\"ForwardTo\", \"\"),\n isnotempty(column_ifexists(\"RedirectTo\", \"\")), column_ifexists(\"RedirectTo\", \"\"),\n isnotempty(column_ifexists(\"ForwardingSmtpAddress\", \"\")), trim_start(@\"smtp:\", column_ifexists(\"ForwardingSmtpAddress\", \"\")),\n \"\"))\n | where isnotempty(DestinationMailAddress)\n | mv-expand split(DestinationMailAddress, \";\")\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIp)[0]\n | extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1])\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP\n | where DistinctUserCount > 1 and EndTime > ago(queryfrequency)\n | mv-expand UserId to typeof(string)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated > ago(queryperiod)\n | where OfficeWorkload =~ \"Exchange\"\n // Uncomment or adjust the following line based on actual field usage\n // | where Operation in (\"Set-Mailbox\", \"New-InboxRule\", \"Set-InboxRule\")\n | where Parameters has_any (\"ForwardTo\", \"RedirectTo\", \"ForwardingSmtpAddress\")\n | mv-apply DynamicParameters = todynamic(Parameters) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source')\n | extend DestinationMailAddress = tolower(case(\n isnotempty(column_ifexists(\"ForwardTo\", \"\")), column_ifexists(\"ForwardTo\", \"\"),\n isnotempty(column_ifexists(\"RedirectTo\", \"\")), column_ifexists(\"RedirectTo\", \"\"),\n isnotempty(column_ifexists(\"ForwardingSmtpAddress\", \"\")), trim_start(@\"smtp:\", column_ifexists(\"ForwardingSmtpAddress\", \"\")),\n \"\"))\n | where isnotempty(DestinationMailAddress)\n | mv-expand split(DestinationMailAddress, \";\")\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIP)[0]\n | extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1])\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP\n | where DistinctUserCount > 1 and EndTime > ago(queryfrequency)\n | mv-expand UserId to typeof(string)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// Combine and Deduplicate Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by tostring(DestinationMailAddress), ClientIP;\n// Final Output\nCombinedEvents\n | project StartTime, EndTime, DestinationMailAddress, ClientIP, DistinctUserCount, UserId, Ports, EventCount, AccountName, AccountUPNSuffix\n", + "queryFrequency": "P1D", + "queryPeriod": "P7D", + "severity": "Medium", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "EnrichedMicrosoft365AuditLogs" + ], + "connectorId": "AzureActiveDirectory" + }, + { + "dataTypes": [ + "OfficeActivity (Exchange)" + ], + "connectorId": "Office365" + } + ], + "tactics": [ + "Collection", + "Exfiltration" + ], + "techniques": [ + "T1114", + "T1020" + ], + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "UserId", + "identifier": "FullName" + }, + { + "columnName": "AccountName", + "identifier": "Name" + }, + { + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "columnName": "ClientIP", + "identifier": "Address" + } + ] + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject9').analyticRuleId9,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 9", + "parentId": "[variables('analyticRuleObject9').analyticRuleId9]", + "contentId": "[variables('analyticRuleObject9')._analyticRulecontentId9]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject9').analyticRuleVersion9]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject9')._analyticRulecontentId9]", + "contentKind": "AnalyticsRule", + "displayName": "GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination", + "contentProductId": "[variables('analyticRuleObject9')._analyticRulecontentProductId9]", + "id": "[variables('analyticRuleObject9')._analyticRulecontentProductId9]", + "version": "[variables('analyticRuleObject9').analyticRuleVersion9]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject10').analyticRuleTemplateSpecName10]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "Office 365 - office_policytampering_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject10').analyticRuleVersion10]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject10')._analyticRulecontentId10]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "Identifies if any tampering is done to either audit log, ATP Safelink, SafeAttachment, AntiPhish, or Dlp policy. \nAn adversary may use this technique to evade detection or avoid other policy-based defenses.\nReferences: https://docs.microsoft.com/powershell/module/exchange/advanced-threat-protection/remove-antiphishrule?view=exchange-ps.", + "displayName": "GSA Enriched Office 365 - Office Policy Tampering", + "enabled": false, + "query": "// Query for EnrichedMicrosoft365AuditLogs\nlet enrichedOpList = EnrichedMicrosoft365AuditLogs \n | summarize by Operation\n | where Operation has_any (\"Remove\", \"Disable\")\n | where Operation contains \"AntiPhish\" \n or Operation contains \"SafeAttachment\" \n or Operation contains \"SafeLinks\" \n or Operation contains \"Dlp\" \n or Operation contains \"Audit\"\n | summarize make_set(Operation, 500);\n\nlet enrichedLogs = EnrichedMicrosoft365AuditLogs\n | where RecordType == \"ExchangeAdmin\"\n | where UserType in~ (\"Admin\", \"DcAdmin\")\n | where Operation in~ (enrichedOpList)\n | extend ClientIPOnly = case( \n ClientIp has \".\", tostring(split(ClientIp, \":\")[0]), \n ClientIp has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIp, \"]\")[0]))),\n ClientIp\n ) \n | extend Port = case(\n ClientIp has \".\", tostring(split(ClientIp, \":\")[1]),\n ClientIp has \"[\", tostring(split(ClientIp, \"]:\")[1]),\n \"\"\n )\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() \n by Operation, UserType, UserId, ClientIP = ClientIPOnly, Port, ResultStatus, Parameters = tostring(AdditionalProperties.Parameters)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Query for OfficeActivity\nlet officeOpList = OfficeActivity \n | summarize by Operation\n | where Operation has_any (\"Remove\", \"Disable\")\n | where Operation contains \"AntiPhish\" \n or Operation contains \"SafeAttachment\" \n or Operation contains \"SafeLinks\" \n or Operation contains \"Dlp\" \n or Operation contains \"Audit\"\n | summarize make_set(Operation, 500);\n\nlet officeLogs = OfficeActivity\n | where RecordType =~ \"ExchangeAdmin\"\n | where UserType in~ (\"Admin\",\"DcAdmin\")\n | where Operation in~ (officeOpList)\n | extend ClientIPOnly = case( \n ClientIP has \".\", tostring(split(ClientIP,\":\")[0]), \n ClientIP has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIP,\"]\")[0]))),\n ClientIP\n ) \n | extend Port = case(\n ClientIP has \".\", tostring(split(ClientIP,\":\")[1]),\n ClientIP has \"[\", tostring(split(ClientIP, \"]:\")[1]),\n \"\"\n )\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() \n by Operation, UserType, UserId, ClientIP = ClientIPOnly, Port, ResultStatus, Parameters\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Combine Enriched Logs and Office Activity Logs\nunion isfuzzy=true enrichedLogs, officeLogs\n| summarize StartTimeUtc = min(StartTimeUtc), EndTimeUtc = max(EndTimeUtc), TotalOperationCount = sum(OperationCount) \n by Operation, UserType, UserId, ClientIP, Port, ResultStatus, Parameters, AccountName, AccountUPNSuffix\n| order by StartTimeUtc desc;\n", + "queryFrequency": "P1D", + "queryPeriod": "P1D", + "severity": "Medium", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "EnrichedMicrosoft365AuditLogs" + ], + "connectorId": "AzureActiveDirectory" + }, + { + "dataTypes": [ + "OfficeActivity (Exchange)" + ], + "connectorId": "Office365" + } + ], + "tactics": [ + "Persistence", + "DefenseEvasion" + ], + "techniques": [ + "T1098", + "T1562" + ], + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "UserId", + "identifier": "FullName" + }, + { + "columnName": "AccountName", + "identifier": "Name" + }, + { + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "columnName": "ClientIP", + "identifier": "Address" + } + ] + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject10').analyticRuleId10,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 10", + "parentId": "[variables('analyticRuleObject10').analyticRuleId10]", + "contentId": "[variables('analyticRuleObject10')._analyticRulecontentId10]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject10').analyticRuleVersion10]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject10')._analyticRulecontentId10]", + "contentKind": "AnalyticsRule", + "displayName": "GSA Enriched Office 365 - Office Policy Tampering", + "contentProductId": "[variables('analyticRuleObject10')._analyticRulecontentProductId10]", + "id": "[variables('analyticRuleObject10')._analyticRulecontentProductId10]", + "version": "[variables('analyticRuleObject10').analyticRuleVersion10]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject11').analyticRuleTemplateSpecName11]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "Office 365 - Office_Uploaded_Executables_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject11').analyticRuleVersion11]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject11')._analyticRulecontentId11]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "Identifies when executable file types are uploaded to Office services such as SharePoint and OneDrive.\nList currently includes exe, inf, gzip, cmd, bat file extensions.\nAdditionally, identifies when a given user is uploading these files to another user's workspace.\nThis may be an indication of a staging location for malware or other malicious activity.", + "displayName": "GSA Enriched Office 365 - New Executable via Office FileUploaded Operation", + "enabled": false, + "query": "// Set query parameters\nlet threshold = 2;\nlet uploadOp = 'FileUploaded';\n// Extensions that are interesting. Add/Remove to this list as you see fit\nlet execExt = dynamic(['exe', 'inf', 'gzip', 'cmd', 'bat']);\nlet starttime = 8d;\nlet endtime = 1d;\n\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated >= ago(endtime)\n | where Operation =~ uploadOp\n | where SourceFileExtension has_any (execExt)\n | extend RecordType = coalesce(tostring(RecordType), \"UnknownRecordType\") // Ensure RecordType is a string\n | project TimeGenerated, OfficeId, OfficeWorkload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, UserAgent, Site_Url, SourceRelativeUrl, SourceFileName\n | join kind=leftanti (\n OfficeActivity\n | where TimeGenerated between (ago(starttime) .. ago(endtime))\n | where Operation =~ uploadOp\n | where SourceFileExtension has_any (execExt)\n | summarize SourceRelativeUrl = make_set(SourceRelativeUrl, 100000), UserId = make_set(UserId, 100000), PrevSeenCount = count() by SourceFileName\n // Uncomment the line below to enforce the threshold\n //| where PrevSeenCount > threshold\n | mvexpand SourceRelativeUrl, UserId\n | extend SourceRelativeUrl = tostring(SourceRelativeUrl), UserId = tostring(UserId) // Ensure consistent types for SourceRelativeUrl and UserId\n ) on SourceFileName, SourceRelativeUrl, UserId\n | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])\n | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), UserAgents = make_list(UserAgent, 100000), OfficeIds = make_list(OfficeId, 100000), SourceRelativeUrls = make_list(SourceRelativeUrl, 100000), FileNames = make_list(tostring(SourceFileName), 100000) // Cast FileNames to string\n by OfficeWorkload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated >= ago(endtime)\n | where Operation == uploadOp\n | extend SourceFileExtension = extract(@\"\\.([^\\./]+)$\", 1, tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)) // Extract file extension\n | where SourceFileExtension in (execExt)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl)\n | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | extend RecordType = coalesce(tostring(RecordType), \"UnknownRecordType\") // Ensure RecordType is a string\n | project TimeGenerated, Id, Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent), Site_Url, SourceRelativeUrl, SourceFileName\n | join kind=leftanti (\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (ago(starttime) .. ago(endtime))\n | where Operation == uploadOp\n | extend SourceFileExtension = extract(@\"\\.([^\\./]+)$\", 1, tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)) // Extract file extension\n | where SourceFileExtension in (execExt)\n | extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl)\n | summarize SourceRelativeUrl = make_set(SourceRelativeUrl, 100000), UserId = make_set(UserId, 100000), PrevSeenCount = count() by SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n // Uncomment the line below to enforce the threshold\n //| where PrevSeenCount > threshold\n | mvexpand SourceRelativeUrl, UserId\n | extend SourceRelativeUrl = tostring(SourceRelativeUrl), UserId = tostring(UserId) // Ensure consistent types for SourceRelativeUrl and UserId\n ) on SourceFileName, SourceRelativeUrl, UserId\n | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])\n | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), UserAgents = make_list(UserAgent, 100000), Ids = make_list(Id, 100000), SourceRelativeUrls = make_list(tostring(SourceRelativeUrl), 100000), FileNames = make_list(tostring(SourceFileName), 100000) // Cast FileNames to string\n by Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIp, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Combine Office and Enriched Logs\nlet CombinedEvents = EnrichedEvents\n | union isfuzzy=true OfficeEvents\n | summarize arg_min(StartTime, *) by tostring(FileNames), UserId, Site_Url, tostring(RecordType) // Ensure FileNames and RecordType are strings\n | order by StartTime desc;\n\n// Final Output\nCombinedEvents\n | project StartTime, EndTime, Workload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder, FileNames, UserAgents, AccountName, AccountUPNSuffix;\n", + "queryFrequency": "P1D", + "queryPeriod": "P8D", + "severity": "Low", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "EnrichedMicrosoft365AuditLogs" + ], + "connectorId": "AzureActiveDirectory" + }, + { + "dataTypes": [ + "OfficeActivity (SharePoint)" + ], + "connectorId": "Office365" + } + ], + "tactics": [ + "CommandAndControl", + "LateralMovement" + ], + "techniques": [ + "T1105", + "T1570" + ], + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "UserId", + "identifier": "FullName" + }, + { + "columnName": "AccountName", + "identifier": "Name" + }, + { + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "columnName": "ClientIP", + "identifier": "Address" + } + ] + }, + { + "entityType": "URL", + "fieldMappings": [ + { + "columnName": "Site_Url", + "identifier": "Url" + } + ] + }, + { + "entityType": "File", + "fieldMappings": [ + { + "columnName": "FileNames", + "identifier": "Name" + } + ] + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject11').analyticRuleId11,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 11", + "parentId": "[variables('analyticRuleObject11').analyticRuleId11]", + "contentId": "[variables('analyticRuleObject11')._analyticRulecontentId11]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject11').analyticRuleVersion11]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject11')._analyticRulecontentId11]", + "contentKind": "AnalyticsRule", + "displayName": "GSA Enriched Office 365 - New Executable via Office FileUploaded Operation", + "contentProductId": "[variables('analyticRuleObject11')._analyticRulecontentProductId11]", + "id": "[variables('analyticRuleObject11')._analyticRulecontentProductId11]", + "version": "[variables('analyticRuleObject11').analyticRuleVersion11]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject12').analyticRuleTemplateSpecName12]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "Office 365 - RareOfficeOperations_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject12').analyticRuleVersion12]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject12')._analyticRulecontentId12]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "Identifies Office operations that are typically rare and can provide capabilities useful to attackers.", + "displayName": "GSA Enriched Office 365 - Rare and Potentially High-Risk Office Operations", + "enabled": false, + "query": "// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where Operation in~ ( \"Add-MailboxPermission\", \"Add-MailboxFolderPermission\", \"Set-Mailbox\", \"New-ManagementRoleAssignment\", \"New-InboxRule\", \"Set-InboxRule\", \"Set-TransportRule\")\n and not(UserId has_any ('NT AUTHORITY\\\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\\\SYSTEM (Microsoft.Exchange.AdminApi.NetCore)', 'NT AUTHORITY\\\\SYSTEM (w3wp)', 'devilfish-applicationaccount') \n and Operation in~ ( \"Add-MailboxPermission\", \"Set-Mailbox\"))\n | extend ClientIPOnly = tostring(extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?', dynamic([\"IPAddress\"]), ClientIP)[0])\n | extend AccountName = tostring(split(UserId, \"@\")[0]), \n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Operation in~ ( \"Add-MailboxPermission\", \"Add-MailboxFolderPermission\", \"Set-Mailbox\", \"New-ManagementRoleAssignment\", \"New-InboxRule\", \"Set-InboxRule\", \"Set-TransportRule\")\n and not(UserId has_any ('NT AUTHORITY\\\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\\\SYSTEM (Microsoft.Exchange.AdminApi.NetCore)', 'NT AUTHORITY\\\\SYSTEM (w3wp)', 'devilfish-applicationaccount') \n and Operation in~ ( \"Add-MailboxPermission\", \"Set-Mailbox\"))\n | extend ClientIPOnly = tostring(extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?', dynamic([\"IPAddress\"]), ClientIp)[0])\n | extend AccountName = tostring(split(UserId, \"@\")[0]), \n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n\n// Combine and Deduplicate Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeGenerated, *) by Operation, UserId, ClientIPOnly;\n\n// Final Output\nCombinedEvents\n | project TimeGenerated, Operation, UserId, AccountName, AccountUPNSuffix, ClientIPOnly\n", + "queryFrequency": "P1D", + "queryPeriod": "P1D", + "severity": "Low", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "EnrichedMicrosoft365AuditLogs" + ], + "connectorId": "AzureActiveDirectory" + }, + { + "dataTypes": [ + "OfficeActivity" + ], + "connectorId": "Office365" + } + ], + "tactics": [ + "Persistence", + "Collection" + ], + "techniques": [ + "T1098", + "T1114" + ], + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "UserId", + "identifier": "FullName" + }, + { + "columnName": "AccountName", + "identifier": "Name" + }, + { + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "columnName": "ClientIPOnly", + "identifier": "Address" + } + ] + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject12').analyticRuleId12,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 12", + "parentId": "[variables('analyticRuleObject12').analyticRuleId12]", + "contentId": "[variables('analyticRuleObject12')._analyticRulecontentId12]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject12').analyticRuleVersion12]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject12')._analyticRulecontentId12]", + "contentKind": "AnalyticsRule", + "displayName": "GSA Enriched Office 365 - Rare and Potentially High-Risk Office Operations", + "contentProductId": "[variables('analyticRuleObject12')._analyticRulecontentProductId12]", + "id": "[variables('analyticRuleObject12')._analyticRulecontentProductId12]", + "version": "[variables('analyticRuleObject12').analyticRuleVersion12]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject13').analyticRuleTemplateSpecName13]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "Office 365 - SharePoint_Downloads_byNewIP_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject13').analyticRuleVersion13]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject13')._analyticRulecontentId13]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "Identifies anomalies using user behavior by setting a threshold for significant changes in file upload/download activities from new IP addresses. It establishes a baseline of typical behavior, compares it to recent activity, and flags deviations exceeding a default threshold of 25.", + "displayName": "GSA Enriched Office 365 - SharePoint File Operation via Previously Unseen IPs", + "enabled": false, + "query": "let threshold = 25;\nlet szSharePointFileOperation = \"SharePointFileOperation\";\nlet szOperations = dynamic([\"FileDownloaded\", \"FileUploaded\"]);\nlet starttime = 14d;\nlet endtime = 1d;\n\n// Define a baseline of normal user behavior for OfficeActivity\nlet userBaselineOffice = OfficeActivity\n | where TimeGenerated between(ago(starttime)..ago(endtime))\n | where RecordType =~ szSharePointFileOperation\n | where Operation in~ (szOperations)\n | where isnotempty(UserAgent)\n | summarize Count = count() by UserId, Operation, Site_Url, ClientIP\n | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url, ClientIP;\n\n// Get recent user activity for OfficeActivity\nlet recentUserActivityOffice = OfficeActivity\n | where TimeGenerated > ago(endtime)\n | where RecordType =~ szSharePointFileOperation\n | where Operation in~ (szOperations)\n | where isnotempty(UserAgent)\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), RecentCount = count() by UserId, UserType, Operation, Site_Url, ClientIP, OfficeObjectId, OfficeWorkload, UserAgent;\n\n// Define a baseline of normal user behavior for EnrichedMicrosoft365AuditLogs\nlet userBaselineEnriched = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between(ago(starttime)..ago(endtime))\n | where RecordType == szSharePointFileOperation\n | where Operation in (szOperations)\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(UserAgent)\n | summarize Count = count() by UserId, Operation, Site_Url, ClientIp\n | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url, ClientIp;\n\n// Get recent user activity for EnrichedMicrosoft365AuditLogs\nlet recentUserActivityEnriched = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated > ago(endtime)\n | where RecordType == szSharePointFileOperation\n | where Operation in (szOperations)\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(UserAgent)\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), RecentCount = count() by UserId, UserType, Operation, Site_Url, ClientIp, ObjectId, Workload, UserAgent;\n\n// Combine user baselines and recent activity, calculate deviation, and deduplicate\nlet UserBehaviorAnalysis = userBaselineOffice\n | join kind=inner (recentUserActivityOffice) on UserId, Operation, Site_Url, ClientIP\n | union (userBaselineEnriched | join kind=inner (recentUserActivityEnriched) on UserId, Operation, Site_Url, ClientIp)\n | extend Deviation = abs(RecentCount - AvgCount) / AvgCount;\n\n// Filter for significant deviations\nUserBehaviorAnalysis\n | where Deviation > threshold\n | project StartTimeUtc, EndTimeUtc, UserId, UserType, Operation, ClientIP, Site_Url, ObjectId, OfficeObjectId, OfficeWorkload, Workload, UserAgent, Deviation, Count = RecentCount\n | order by Count desc, ClientIP asc, Operation asc, UserId asc\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n", + "queryFrequency": "P1D", + "queryPeriod": "P14D", + "severity": "Medium", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "EnrichedMicrosoft365AuditLogs" + ], + "connectorId": "AzureActiveDirectory" + }, + { + "dataTypes": [ + "OfficeActivity (SharePoint)" + ], + "connectorId": "Office365" + } + ], + "tactics": [ + "Exfiltration" + ], + "techniques": [ + "T1030" + ], + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "UserId", + "identifier": "FullName" + }, + { + "columnName": "AccountName", + "identifier": "Name" + }, + { + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "columnName": "ClientIP", + "identifier": "Address" + } + ] + }, + { + "entityType": "URL", + "fieldMappings": [ + { + "columnName": "Site_Url", + "identifier": "Url" + } + ] + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject13').analyticRuleId13,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 13", + "parentId": "[variables('analyticRuleObject13').analyticRuleId13]", + "contentId": "[variables('analyticRuleObject13')._analyticRulecontentId13]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject13').analyticRuleVersion13]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject13')._analyticRulecontentId13]", + "contentKind": "AnalyticsRule", + "displayName": "GSA Enriched Office 365 - SharePoint File Operation via Previously Unseen IPs", + "contentProductId": "[variables('analyticRuleObject13')._analyticRulecontentProductId13]", + "id": "[variables('analyticRuleObject13')._analyticRulecontentProductId13]", + "version": "[variables('analyticRuleObject13').analyticRuleVersion13]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject14').analyticRuleTemplateSpecName14]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "Office 365 - SharePoint_Downloads_byNewUserAgent_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject14').analyticRuleVersion14]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject14')._analyticRulecontentId14]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "Identifies anomalies if the number of documents uploaded or downloaded from device(s) associated with a previously unseen user agent exceeds a threshold (default is 5) and deviation (default is 25%).", + "displayName": "GSA Enriched Office 365 - SharePointFileOperation via devices with previously unseen user agents", + "enabled": false, + "query": "let threshold = 5;\nlet szSharePointFileOperation = \"SharePointFileOperation\";\nlet szOperations = dynamic([\"FileDownloaded\", \"FileUploaded\"]);\nlet starttime = 14d;\nlet endtime = 1d;\n\n// OfficeActivity - Base Events\nlet BaseeventsOffice = OfficeActivity\n | where TimeGenerated between (ago(starttime)..ago(endtime))\n | where RecordType =~ szSharePointFileOperation\n | where Operation in~ (szOperations)\n | where isnotempty(UserAgent);\n\n// OfficeActivity - Frequent User Agents\nlet FrequentUAOffice = BaseeventsOffice\n | summarize FUACount = count() by UserAgent, RecordType, Operation\n | where FUACount >= threshold\n | distinct UserAgent;\n\n// OfficeActivity - User Baseline\nlet UserBaseLineOffice = BaseeventsOffice\n | summarize Count = count() by UserId, Operation, Site_Url\n | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url;\n\n// OfficeActivity - Recent User Activity\nlet RecentActivityOffice = OfficeActivity\n | where TimeGenerated > ago(endtime)\n | where RecordType =~ szSharePointFileOperation\n | where Operation in~ (szOperations)\n | where isnotempty(UserAgent)\n | where UserAgent in~ (FrequentUAOffice)\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OfficeObjectIdCount = dcount(OfficeObjectId), OfficeObjectIdList = make_set(OfficeObjectId), UserAgentSeenCount = count()\n by RecordType, Operation, UserAgent, UserType, UserId, ClientIP , OfficeWorkload, Site_Url;\n\n// EnrichedMicrosoft365AuditLogs - Base Events\nlet BaseeventsEnriched = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (ago(starttime)..ago(endtime))\n | where RecordType == szSharePointFileOperation\n | where Operation in (szOperations)\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(UserAgent);\n\n// EnrichedMicrosoft365AuditLogs - Frequent User Agents\nlet FrequentUAEnriched = BaseeventsEnriched\n | summarize FUACount = count() by UserAgent, RecordType, Operation\n | where FUACount >= threshold\n | distinct UserAgent;\n\n// EnrichedMicrosoft365AuditLogs - User Baseline\nlet UserBaseLineEnriched = BaseeventsEnriched\n | summarize Count = count() by UserId, Operation, Site_Url\n | summarize AvgCount = avg(Count) by UserId, Operation, Site_Url;\n\n// EnrichedMicrosoft365AuditLogs - Recent User Activity\nlet RecentActivityEnriched = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated > ago(endtime)\n | where RecordType == szSharePointFileOperation\n | where Operation in (szOperations)\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | extend ClientIP = ClientIp\n | where isnotempty(UserAgent)\n | where UserAgent in (FrequentUAEnriched)\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ObjectIdCount = dcount(ObjectId), ObjectIdList = make_set(ObjectId), UserAgentSeenCount = count()\n by RecordType, Operation, UserAgent, UserId,ClientIP, Site_Url;\n\n// Combine Baseline and Recent Activity, Calculate Deviation, and Deduplicate\nlet UserBehaviorAnalysisOffice = UserBaseLineOffice\n | join kind=inner (RecentActivityOffice) on UserId, Operation, Site_Url\n | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount;\n\nlet UserBehaviorAnalysisEnriched = UserBaseLineEnriched\n | join kind=inner (RecentActivityEnriched) on UserId, Operation, Site_Url\n | extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount;\n\n// Combine Office and Enriched Logs\nlet CombinedUserBehaviorAnalysis = UserBehaviorAnalysisOffice\n | union UserBehaviorAnalysisEnriched;\n\n// Filter and Format Final Results\nCombinedUserBehaviorAnalysis\n | where Deviation > 0.25\n | extend UserIdName = tostring(split(UserId, '@')[0]), \n UserIdUPNSuffix = tostring(split(UserId, '@')[1])\n | project-reorder StartTimeUtc, EndTimeUtc, UserAgent, UserAgentSeenCount, UserId, ClientIP, Site_Url\n | order by UserAgentSeenCount desc, UserAgent asc, UserId asc, Site_Url asc\n", + "queryFrequency": "P1D", + "queryPeriod": "P14D", + "severity": "Medium", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "EnrichedMicrosoft365AuditLogs" + ], + "connectorId": "AzureActiveDirectory" + }, + { + "dataTypes": [ + "OfficeActivity (SharePoint)" + ], + "connectorId": "Office365" + } + ], + "tactics": [ + "Exfiltration" + ], + "techniques": [ + "T1030" + ], + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "UserId", + "identifier": "FullName" + }, + { + "columnName": "UserIdName", + "identifier": "Name" + }, + { + "columnName": "UserIdUPNSuffix", + "identifier": "UPNSuffix" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "columnName": "ClientIP", + "identifier": "Address" + } + ] + }, + { + "entityType": "URL", + "fieldMappings": [ + { + "columnName": "Site_Url", + "identifier": "Url" + } + ] + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject14').analyticRuleId14,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 14", + "parentId": "[variables('analyticRuleObject14').analyticRuleId14]", + "contentId": "[variables('analyticRuleObject14')._analyticRulecontentId14]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject14').analyticRuleVersion14]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject14')._analyticRulecontentId14]", + "contentKind": "AnalyticsRule", + "displayName": "GSA Enriched Office 365 - SharePointFileOperation via devices with previously unseen user agents", + "contentProductId": "[variables('analyticRuleObject14')._analyticRulecontentProductId14]", + "id": "[variables('analyticRuleObject14')._analyticRulecontentProductId14]", + "version": "[variables('analyticRuleObject14').analyticRuleVersion14]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject15').analyticRuleTemplateSpecName15]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "Office 365 - sharepoint_file_transfer_above_threshold_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject15').analyticRuleVersion15]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject15')._analyticRulecontentId15]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "Identifies Office365 SharePoint file transfers above a certain threshold in a 15-minute time period.\nPlease note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur.", + "displayName": "GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold", + "enabled": false, + "query": "let threshold = 5000;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload has_any(\"SharePoint\", \"OneDrive\")\n | where Operation has_any(\"FileDownloaded\", \"FileSyncDownloadedFull\", \"FileSyncUploadedFull\", \"FileUploaded\")\n | extend ClientIP = ClientIp\n | summarize TransferCount = dcount(ObjectId), fileslist = make_set(ObjectId, 10000)\n by UserId, ClientIP, bin(TimeGenerated, 15m)\n | where TransferCount >= threshold\n | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]),\n strcat(\"SeeFilesListField_\", tostring(hash(tostring(fileslist)))))\n | extend AccountName = tostring(split(UserId, \"@\")[0]),\n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where EventSource == \"SharePoint\"\n | where OfficeWorkload has_any(\"SharePoint\", \"OneDrive\")\n | where Operation has_any(\"FileDownloaded\", \"FileSyncDownloadedFull\", \"FileSyncUploadedFull\", \"FileUploaded\")\n | summarize TransferCount = dcount(OfficeObjectId), fileslist = make_set(OfficeObjectId, 10000)\n by UserId, ClientIP, bin(TimeGenerated, 15m)\n | where TransferCount >= threshold\n | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]),\n strcat(\"SeeFilesListField_\", tostring(hash(tostring(fileslist)))))\n | extend AccountName = tostring(split(UserId, \"@\")[0]),\n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeGenerated, *) by UserId, ClientIP;\n// Final Output\nCombinedEvents\n | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, TransferCount, fileslist\n | order by TimeGenerated desc\n", + "queryFrequency": "PT15M", + "queryPeriod": "PT15M", + "severity": "Medium", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "EnrichedMicrosoft365AuditLogs" + ], + "connectorId": "AzureActiveDirectory" + }, + { + "dataTypes": [ + "OfficeActivity (SharePoint)" + ], + "connectorId": "Office365" + } + ], + "tactics": [ + "Exfiltration" + ], + "techniques": [ + "T1020" + ], + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "UserId", + "identifier": "FullName" + }, + { + "columnName": "AccountName", + "identifier": "Name" + }, + { + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "columnName": "ClientIP", + "identifier": "Address" + } + ] + }, + { + "entityType": "File", + "fieldMappings": [ + { + "columnName": "FileSample", + "identifier": "Name" + } + ] + } + ], + "customDetails": { + "TransferCount": "TransferCount", + "FilesList": "fileslist" + }, + "incidentConfiguration": { + "groupingConfiguration": { + "reopenClosedIncident": false, + "enabled": true, + "lookbackDuration": "PT5H", + "matchingMethod": "Selected", + "groupByEntities": [ + "Account" + ] + }, + "createIncident": true + } + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject15').analyticRuleId15,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 15", + "parentId": "[variables('analyticRuleObject15').analyticRuleId15]", + "contentId": "[variables('analyticRuleObject15')._analyticRulecontentId15]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject15').analyticRuleVersion15]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject15')._analyticRulecontentId15]", + "contentKind": "AnalyticsRule", + "displayName": "GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold", + "contentProductId": "[variables('analyticRuleObject15')._analyticRulecontentProductId15]", + "id": "[variables('analyticRuleObject15')._analyticRulecontentProductId15]", + "version": "[variables('analyticRuleObject15').analyticRuleVersion15]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject16').analyticRuleTemplateSpecName16]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "Office 365 - sharepoint_file_transfer_folders_above_threshold_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject16').analyticRuleVersion16]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject16')._analyticRulecontentId16]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "Identifies Office365 SharePoint file transfers with a distinct folder count above a certain threshold in a 15-minute time period. Please note that entity mapping for arrays is not supported, so when there is a single value in an array, we will pull that value from the array as a single string to populate the entity to support entity mapping features within Sentinel. Additionally, if the array is multivalued, we will input a string to indicate this with a unique hash so that matching will not occur.", + "displayName": "GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold", + "enabled": false, + "query": "let threshold = 5000;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload has_any(\"SharePoint\", \"OneDrive\")\n | where Operation has_any(\"FileDownloaded\", \"FileSyncDownloadedFull\", \"FileSyncUploadedFull\", \"FileUploaded\")\n | extend UserAgent = tostring(AdditionalProperties[\"UserAgent\"])\n | summarize count_distinct_ObjectId = dcount(ObjectId),\n fileslist = make_set(ObjectId, 10000),\n any_UserAgent = any(UserAgent)\n by UserId, ClientIP = ClientIp, bin(TimeGenerated, 15m)\n | where count_distinct_ObjectId >= threshold\n | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]),\n strcat(\"SeeFilesListField_\", tostring(hash(tostring(fileslist)))))\n | extend AccountName = tostring(split(UserId, \"@\")[0]),\n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where EventSource == \"SharePoint\"\n | where OfficeWorkload has_any(\"SharePoint\", \"OneDrive\")\n | where Operation has_any(\"FileDownloaded\", \"FileSyncDownloadedFull\", \"FileSyncUploadedFull\", \"FileUploaded\")\n | summarize count_distinct_ObjectId = dcount(OfficeObjectId),\n fileslist = make_set(OfficeObjectId, 10000),\n any_UserAgent = any(UserAgent)\n by UserId, ClientIP, bin(TimeGenerated, 15m)\n | where count_distinct_ObjectId >= threshold\n | extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]),\n strcat(\"SeeFilesListField_\", tostring(hash(tostring(fileslist)))))\n | extend AccountName = tostring(split(UserId, \"@\")[0]),\n AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeGenerated, *) by UserId, ClientIP, any_UserAgent;\n// Final Output\nCombinedEvents\n | extend TransferCount = count_distinct_ObjectId\n | project TimeGenerated, UserId, ClientIP, AccountName, AccountUPNSuffix, FileSample, UserAgent = any_UserAgent, TransferCount, fileslist\n | order by TimeGenerated desc\n", + "queryFrequency": "PT15M", + "queryPeriod": "PT15M", + "severity": "Medium", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "EnrichedMicrosoft365AuditLogs" + ], + "connectorId": "AzureActiveDirectory" + }, + { + "dataTypes": [ + "OfficeActivity (SharePoint)" + ], + "connectorId": "Office365" + } + ], + "tactics": [ + "Exfiltration" + ], + "techniques": [ + "T1020" + ], + "entityMappings": [ + { + "entityType": "Account", + "fieldMappings": [ + { + "columnName": "UserId", + "identifier": "FullName" + }, + { + "columnName": "AccountName", + "identifier": "Name" + }, + { + "columnName": "AccountUPNSuffix", + "identifier": "UPNSuffix" + } + ] + }, + { + "entityType": "IP", + "fieldMappings": [ + { + "columnName": "ClientIP", + "identifier": "Address" + } + ] + }, + { + "entityType": "File", + "fieldMappings": [ + { + "columnName": "FileSample", + "identifier": "Name" + } + ] + } + ], + "customDetails": { + "TransferCount": "TransferCount", + "FilesList": "fileslist" + }, + "incidentConfiguration": { + "groupingConfiguration": { + "reopenClosedIncident": false, + "enabled": true, + "lookbackDuration": "PT5H", + "matchingMethod": "Selected", + "groupByEntities": [ + "Account" + ] + }, + "createIncident": true + } + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject16').analyticRuleId16,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 16", + "parentId": "[variables('analyticRuleObject16').analyticRuleId16]", + "contentId": "[variables('analyticRuleObject16')._analyticRulecontentId16]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject16').analyticRuleVersion16]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject16')._analyticRulecontentId16]", + "contentKind": "AnalyticsRule", + "displayName": "GSA Enriched Office 365 - Sharepoint File Transfer Above Threshold", + "contentProductId": "[variables('analyticRuleObject16')._analyticRulecontentProductId16]", + "id": "[variables('analyticRuleObject16')._analyticRulecontentProductId16]", + "version": "[variables('analyticRuleObject16').analyticRuleVersion16]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject17').analyticRuleTemplateSpecName17]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "SWG - Abnormal Deny Rate_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject17').analyticRuleVersion17]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject17')._analyticRulecontentId17]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "Identifies abnormal deny rate for specific source IP to destination IP based on the normal average and standard deviation learned during a configured period. This can indicate potential exfiltration, initial access, or C2, where an attacker tries to exploit the same vulnerability on machines in the organization but is being blocked by firewall rules.", + "displayName": "Detect Abnormal Deny Rate for Source to Destination IP", + "enabled": false, + "query": "let NumOfStdsThreshold = 3;\nlet LearningPeriod = 5d;\nlet BinTime = 1h;\nlet MinThreshold = 5.0;\nlet MinLearningBuckets = 5;\nlet TrafficLogs = NetworkAccessTraffic\n | where Action == 'Denied'\n | where isnotempty(DestinationIp) and isnotempty(SourceIp);\nlet LearningSrcIpDenyRate = TrafficLogs\n | where TimeGenerated between (ago(LearningPeriod + 1d) .. ago(1d))\n | summarize count() by SourceIp, bin(TimeGenerated, BinTime), DestinationIp\n | summarize LearningTimeSrcIpDenyRateAvg = avg(count_), LearningTimeSrcIpDenyRateStd = stdev(count_), LearningTimeBuckets = count() by SourceIp, DestinationIp\n | where LearningTimeBuckets > MinLearningBuckets;\nlet AlertTimeSrcIpDenyRate = TrafficLogs\n | where TimeGenerated between (ago(1h) .. now())\n | summarize AlertTimeSrcIpDenyRateCount = count() by SourceIp, DestinationIp;\nAlertTimeSrcIpDenyRate\n | join kind=leftouter (LearningSrcIpDenyRate) on SourceIp, DestinationIp\n | extend LearningThreshold = max_of(LearningTimeSrcIpDenyRateAvg + NumOfStdsThreshold * LearningTimeSrcIpDenyRateStd, MinThreshold)\n | where AlertTimeSrcIpDenyRateCount > LearningThreshold\n | project SourceIp, DestinationIp, AlertTimeSrcIpDenyRateCount, LearningThreshold \n", + "queryFrequency": "PT1H", + "queryPeriod": "PT25H", + "severity": "Medium", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 1, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "NetworkAccessTrafficLogs" + ], + "connectorId": "AzureActiveDirectory" + } + ], + "tactics": [ + "InitialAccess", + "Exfiltration", + "CommandAndControl" + ], + "entityMappings": [ + { + "entityType": "IP", + "fieldMappings": [ + { + "columnName": "SourceIp", + "identifier": "Address" + } + ] + }, + { + "entityType": "URL", + "fieldMappings": [ + { + "columnName": "DestinationIp", + "identifier": "Url" + } + ] + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject17').analyticRuleId17,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 17", + "parentId": "[variables('analyticRuleObject17').analyticRuleId17]", + "contentId": "[variables('analyticRuleObject17')._analyticRulecontentId17]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject17').analyticRuleVersion17]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject17')._analyticRulecontentId17]", + "contentKind": "AnalyticsRule", + "displayName": "Detect Abnormal Deny Rate for Source to Destination IP", + "contentProductId": "[variables('analyticRuleObject17')._analyticRulecontentProductId17]", + "id": "[variables('analyticRuleObject17')._analyticRulecontentProductId17]", + "version": "[variables('analyticRuleObject17').analyticRuleVersion17]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject18').analyticRuleTemplateSpecName18]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "SWG - Abnormal Port to Protocol_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject18').analyticRuleVersion18]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject18')._analyticRulecontentId18]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "Identifies changes in the protocol used for specific destination ports, comparing the current runtime with a learned baseline. This can indicate potential protocol misuse or configuration changes.", + "displayName": "Detect Protocol Changes for Destination Ports", + "enabled": false, + "query": "let LearningPeriod = 7d;\nlet RunTime = 1d;\nlet StartLearningPeriod = ago(LearningPeriod + RunTime);\nlet EndRunTime = ago(RunTime);\nlet LearningPortToProtocol = \n NetworkAccessTraffic\n | where TimeGenerated between (StartLearningPeriod .. EndRunTime)\n | where isnotempty(DestinationPort)\n | summarize LearningTimeCount = count() by LearningTimeDstPort = DestinationPort, LearningTimeProtocol = TransportProtocol, SourceIp, DestinationFqdn;\nlet AlertTimePortToProtocol = \n NetworkAccessTraffic\n | where TimeGenerated between (EndRunTime .. now())\n | where isnotempty(DestinationPort)\n | summarize AlertTimeCount = count() by AlertTimeDstPort = DestinationPort, AlertTimeProtocol = TransportProtocol, SourceIp, DestinationFqdn;\nAlertTimePortToProtocol\n | join kind=leftouter (LearningPortToProtocol) on $left.AlertTimeDstPort == $right.LearningTimeDstPort and $left.SourceIp == $right.SourceIp and $left.DestinationFqdn == $right.DestinationFqdn\n | where isnull(LearningTimeProtocol) or LearningTimeProtocol != AlertTimeProtocol\n | project AlertTimeDstPort, AlertTimeProtocol, LearningTimeProtocol, SourceIp, DestinationFqdn\n | extend IPCustomEntity = SourceIp, FqdnCustomEntity = DestinationFqdn\n", + "queryFrequency": "PT1H", + "queryPeriod": "P8D", + "severity": "Medium", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 1, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "EnrichedMicrosoft365AuditLogs" + ], + "connectorId": "AzureActiveDirectory" + } + ], + "tactics": [ + "DefenseEvasion", + "Exfiltration", + "CommandAndControl" + ], + "entityMappings": [ + { + "entityType": "IP", + "fieldMappings": [ + { + "columnName": "IPCustomEntity", + "identifier": "Address" + } + ] + }, + { + "entityType": "URL", + "fieldMappings": [ + { + "columnName": "FqdnCustomEntity", + "identifier": "Url" + } + ] + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject18').analyticRuleId18,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 18", + "parentId": "[variables('analyticRuleObject18').analyticRuleId18]", + "contentId": "[variables('analyticRuleObject18')._analyticRulecontentId18]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject18').analyticRuleVersion18]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject18')._analyticRulecontentId18]", + "contentKind": "AnalyticsRule", + "displayName": "Detect Protocol Changes for Destination Ports", + "contentProductId": "[variables('analyticRuleObject18')._analyticRulecontentProductId18]", + "id": "[variables('analyticRuleObject18')._analyticRulecontentProductId18]", + "version": "[variables('analyticRuleObject18').analyticRuleVersion18]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleObject19').analyticRuleTemplateSpecName19]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "SWG - Source IP Port Scan_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleObject19').analyticRuleVersion19]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRuleObject19')._analyticRulecontentId19]", + "apiVersion": "2023-02-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "Identifies a source IP scanning multiple open ports on Global Secure Access Firewall. This can indicate malicious scanning of ports by an attacker, trying to reveal open ports in the organization that can be compromised for initial access.", + "displayName": "Detect Source IP Scanning Multiple Open Ports", + "enabled": false, + "query": "let port_scan_time = 30s;\nlet min_ports_threshold = 100;\nNetworkAccessTraffic\n| where TimeGenerated > ago(1d)\n| where Action == 'Allowed'\n| summarize PortsScanned = dcount(DestinationPort) by SourceIp, bin(TimeGenerated, port_scan_time)\n| where PortsScanned > min_ports_threshold\n| project SourceIp, PortsScanned, TimeGenerated\n", + "queryFrequency": "P1D", + "queryPeriod": "P1D", + "severity": "Medium", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 1, + "status": "Available", + "requiredDataConnectors": [ + { + "dataTypes": [ + "EnrichedMicrosoft365AuditLogs" + ], + "connectorId": "AzureActiveDirectory" + } + ], + "tactics": [ + "Discovery" + ], + "techniques": [ + "T1046" + ], + "entityMappings": [ + { + "entityType": "IP", + "fieldMappings": [ + { + "columnName": "SourceIp", + "identifier": "Address" + } + ] + }, + { + "entityType": "URL", + "fieldMappings": [ + { + "columnName": "Fqdn", + "identifier": "Url" + } + ] + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleObject19').analyticRuleId19,'/'))))]", + "properties": { + "description": "Global Secure Access Analytics Rule 19", + "parentId": "[variables('analyticRuleObject19').analyticRuleId19]", + "contentId": "[variables('analyticRuleObject19')._analyticRulecontentId19]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleObject19').analyticRuleVersion19]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('analyticRuleObject19')._analyticRulecontentId19]", + "contentKind": "AnalyticsRule", + "displayName": "Detect Source IP Scanning Multiple Open Ports", + "contentProductId": "[variables('analyticRuleObject19')._analyticRulecontentProductId19]", + "id": "[variables('analyticRuleObject19')._analyticRulecontentProductId19]", + "version": "[variables('analyticRuleObject19').analyticRuleVersion19]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject1').huntingQueryTemplateSpecName1]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "AnomolousUserAccessingOtherUsersMailbox_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject1').huntingQueryVersion1]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_1", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "GSA Enriched Office 365 - Anomalous access to other users' mailboxes", + "category": "Hunting Queries", + "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = totimespan((endtime - starttime) * 2);\nlet user_threshold = 1; // Threshold for number of mailboxes accessed\nlet folder_threshold = 5; // Threshold for number of mailbox folders accessed\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated between (ago(lookback)..starttime)\n | where Operation =~ \"MailItemsAccessed\"\n | where ResultStatus =~ \"Succeeded\"\n | where tolower(MailboxOwnerUPN) != tolower(UserId)\n | join kind=rightanti (\n OfficeActivity\n | where TimeGenerated between (starttime..endtime)\n | where Operation =~ \"MailItemsAccessed\"\n | where ResultStatus =~ \"Succeeded\"\n | where tolower(MailboxOwnerUPN) != tolower(UserId)\n ) on MailboxOwnerUPN, UserId\n | where isnotempty(Folders)\n | mv-expand parse_json(Folders)\n | extend folders = tostring(Folders.Path)\n | extend ClientIP = iif(Client_IPAddress startswith \"[\", extract(\"\\\\[([^\\\\]]*)\", 1, Client_IPAddress), Client_IPAddress)\n | summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), set_folders = make_set(folders, 100000), set_ClientInfoString = make_set(ClientInfoString, 100000), set_ClientIP = make_set(ClientIP, 100000), set_MailboxGuid = make_set(MailboxGuid, 100000), set_MailboxOwnerUPN = make_set(MailboxOwnerUPN, 100000) by UserId\n | extend folder_count = array_length(set_folders)\n | extend user_count = array_length(set_MailboxGuid)\n | where user_count > user_threshold or folder_count > folder_threshold\n | extend Reason = case(user_count > user_threshold and folder_count > folder_threshold, \"Both User and Folder Threshold Exceeded\", folder_count > folder_threshold and user_count < user_threshold, \"Folder Count Threshold Exceeded\", \"User Threshold Exceeded\")\n | sort by user_count desc\n | project-reorder UserId, user_count, folder_count, set_MailboxOwnerUPN, set_ClientIP, set_ClientInfoString, set_folders\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (ago(lookback)..starttime)\n | where Operation =~ \"MailItemsAccessed\"\n | where ResultStatus =~ \"Succeeded\"\n | extend MailboxOwnerUPN = tostring(parse_json(AdditionalProperties).MailboxOwnerUPN)\n | where tolower(MailboxOwnerUPN) != tolower(UserId)\n | join kind=rightanti (\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime..endtime)\n | where Operation =~ \"MailItemsAccessed\"\n | where ResultStatus =~ \"Succeeded\"\n | extend MailboxOwnerUPN = tostring(parse_json(AdditionalProperties).MailboxOwnerUPN)\n | where tolower(MailboxOwnerUPN) != tolower(UserId)\n ) on MailboxOwnerUPN, UserId\n | where isnotempty(tostring(parse_json(AdditionalProperties).Folders))\n | mv-expand Folders = parse_json(AdditionalProperties).Folders\n | extend folders = tostring(Folders.Path)\n | extend ClientIP = iif(ClientIp startswith \"[\", extract(\"\\\\[([^\\\\]]*)\", 1, ClientIp), ClientIp)\n | extend ClientInfoString = tostring(parse_json(AdditionalProperties).ClientInfoString)\n | extend MailboxGuid = tostring(parse_json(AdditionalProperties).MailboxGuid)\n | summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), set_folders = make_set(folders, 100000), set_ClientInfoString = make_set(ClientInfoString, 100000), set_ClientIP = make_set(ClientIP, 100000), set_MailboxGuid = make_set(MailboxGuid, 100000), set_MailboxOwnerUPN = make_set(MailboxOwnerUPN, 100000) by UserId\n | extend folder_count = array_length(set_folders)\n | extend user_count = array_length(set_MailboxGuid)\n | where user_count > user_threshold or folder_count > folder_threshold\n | extend Reason = case(user_count > user_threshold and folder_count > folder_threshold, \"Both User and Folder Threshold Exceeded\", folder_count > folder_threshold and user_count < user_threshold, \"Folder Count Threshold Exceeded\", \"User Threshold Exceeded\")\n | sort by user_count desc\n | project-reorder UserId, user_count, folder_count, set_MailboxOwnerUPN, set_ClientIP, set_ClientInfoString, set_folders\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by UserId, ClientIP;\n// Final Output\nCombinedEvents\n | project UserId, user_count, folder_count, set_MailboxOwnerUPN, set_ClientIP, set_ClientInfoString, set_folders, AccountName, AccountUPNSuffix\n | order by user_count desc\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "Looks for users accessing multiple other users' mailboxes or accessing multiple folders in another users mailbox." + }, + { + "name": "tactics", + "value": "Collection" + }, + { + "name": "techniques", + "value": "T1114.002" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject1')._huntingQuerycontentId1),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 1", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject1')._huntingQuerycontentId1)]", + "contentId": "[variables('huntingQueryObject1')._huntingQuerycontentId1]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject1').huntingQueryVersion1]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject1')._huntingQuerycontentId1]", + "contentKind": "HuntingQuery", + "displayName": "GSA Enriched Office 365 - Anomalous access to other users' mailboxes", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject1')._huntingQuerycontentId1,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject1')._huntingQuerycontentId1,'-', '2.0.2')))]", + "version": "2.0.2" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject2').huntingQueryTemplateSpecName2]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "double_file_ext_exes_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject2').huntingQueryVersion2]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_2", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "Exes with double file extension and access summary", + "category": "Hunting Queries", + "query": "let known_ext = dynamic([\"lnk\", \"log\", \"option\", \"config\", \"manifest\", \"partial\"]);\nlet excluded_users = dynamic([\"app@sharepoint\"]);\nEnrichedMicrosoft365AuditLogs\n| where RecordType == \"SharePointFileOperation\" and isnotempty(ObjectId)\n| where ObjectId has \".exe.\" \n and not(ObjectId endswith \".lnk\") \n and not(ObjectId endswith \".log\") \n and not(ObjectId endswith \".option\") \n and not(ObjectId endswith \".config\") \n and not(ObjectId endswith \".manifest\") \n and not(ObjectId endswith \".partial\")\n| extend Extension = extract(\"[^.]*\\\\.[^.]*$\", 0, ObjectId)\n| extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n| join kind=leftouter (\n EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\" and (Operation == \"FileDownloaded\" or Operation == \"FileAccessed\")\n | where not(ObjectId endswith \".lnk\") \n and not(ObjectId endswith \".log\") \n and not(ObjectId endswith \".option\") \n and not(ObjectId endswith \".config\") \n and not(ObjectId endswith \".manifest\") \n and not(ObjectId endswith \".partial\")\n) on ObjectId\n| where UserId1 !in (excluded_users)\n| extend userBag = bag_pack(\"UserId\", UserId1, \"ClientIp\", ClientIp1)\n| summarize make_set(UserId1, 10000), userBag = make_bag(userBag), UploadTime = max(TimeGenerated) by UserId, ObjectId, SourceFileName, Extension\n| extend NumberOfUsers = array_length(bag_keys(userBag))\n| project UploadTime, Uploader = UserId, FileLocation = ObjectId, FileName = SourceFileName, AccessedBy = userBag, Extension, NumberOfUsers\n| extend UploaderName = tostring(split(Uploader, \"@\")[0]), UploaderUPNSuffix = tostring(split(Uploader, \"@\")[1])\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "Provides a summary of executable files with double file extensions in SharePoint \n and the users and IP addresses that have accessed them." + }, + { + "name": "tactics", + "value": "DefenseEvasion" + }, + { + "name": "techniques", + "value": "T1036" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject2')._huntingQuerycontentId2),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 2", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject2')._huntingQuerycontentId2)]", + "contentId": "[variables('huntingQueryObject2')._huntingQuerycontentId2]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject2').huntingQueryVersion2]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject2')._huntingQuerycontentId2]", + "contentKind": "HuntingQuery", + "displayName": "Exes with double file extension and access summary", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject2')._huntingQuerycontentId2,'-', '2.0.1')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject2')._huntingQuerycontentId2,'-', '2.0.1')))]", + "version": "2.0.1" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject3').huntingQueryTemplateSpecName3]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "ExternalUserAddedRemovedInTeams_HuntVersion_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject3').huntingQueryVersion3]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_3", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "External User Added and Removed in a Short Timeframe", + "category": "Hunting Queries", + "query": "// If you want to look at user added further than 7 days ago adjust this value\n// If you want to change the timeframe of how quickly accounts need to be added and removed change this value\nlet time_delta = 1h;\nEnrichedMicrosoft365AuditLogs\n| where Workload == \"MicrosoftTeams\"\n| where Operation == \"MemberAdded\"\n| extend UPN = tostring(parse_json(tostring(AdditionalProperties)).UPN) // Assuming UPN is stored in AdditionalProperties\n| where UPN contains \"#EXT#\"\n| project TimeAdded = TimeGenerated, Operation, UPN, UserWhoAdded = UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName), TeamGuid = tostring(parse_json(tostring(AdditionalProperties)).TeamGuid)\n| join kind=innerunique (\n EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"MemberRemoved\"\n | extend UPN = tostring(parse_json(tostring(AdditionalProperties)).UPN) // Assuming UPN is stored in AdditionalProperties\n | where UPN contains \"#EXT#\"\n | project TimeDeleted = TimeGenerated, Operation, UPN, UserWhoDeleted = UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName), TeamGuid = tostring(parse_json(tostring(AdditionalProperties)).TeamGuid)\n) on UPN, TeamGuid\n| where TimeDeleted < (TimeAdded + time_delta)\n| project TimeAdded, TimeDeleted, UPN, UserWhoAdded, UserWhoDeleted, TeamName, TeamGuid\n| extend AccountName = tostring(split(UPN, \"@\")[0]), AccountUPNSuffix = tostring(split(UPN, \"@\")[1])\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "This hunting query identifies external user accounts that are added to a Team and then removed within one hour." + }, + { + "name": "tactics", + "value": "Persistence" + }, + { + "name": "techniques", + "value": "T1136" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject3')._huntingQuerycontentId3),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 3", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject3')._huntingQuerycontentId3)]", + "contentId": "[variables('huntingQueryObject3')._huntingQuerycontentId3]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject3').huntingQueryVersion3]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject3')._huntingQuerycontentId3]", + "contentKind": "HuntingQuery", + "displayName": "External User Added and Removed in a Short Timeframe", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject3')._huntingQuerycontentId3,'-', '2.0.1')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject3')._huntingQuerycontentId3,'-', '2.0.1')))]", + "version": "2.0.1" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject4').huntingQueryTemplateSpecName4]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "ExternalUserFromNewOrgAddedToTeams_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject4').huntingQueryVersion4]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_4", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "GSA Enriched Office 365 - External user from a new organisation added to Teams", + "category": "Hunting Queries", + "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = totimespan((endtime - starttime) * 7);\n// OfficeActivity Known Organizations\nlet known_orgs_office = (\n OfficeActivity\n | where TimeGenerated between(ago(lookback)..starttime)\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberAdded\" or Operation =~ \"TeamsSessionStarted\"\n | extend UPN = iif(Operation == \"MemberAdded\", tostring(Members[0].UPN), UserId)\n | extend Organization = tostring(split(split(UPN, \"_\")[1], \"#\")[0])\n | where isnotempty(Organization)\n | summarize by Organization\n);\n// OfficeActivity Query for New Organizations\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated between(starttime..endtime)\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberAdded\"\n | extend UPN = tostring(parse_json(Members)[0].UPN)\n | extend Organization = tostring(split(split(UPN, \"_\")[1], \"#\")[0])\n | where isnotempty(Organization)\n | where Organization !in (known_orgs_office)\n | extend AccountName = tostring(split(UPN, \"@\")[0]), AccountUPNSuffix = tostring(split(UPN, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n// EnrichedMicrosoft365AuditLogs Known Organizations\nlet known_orgs_enriched = (\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between(ago(lookback)..starttime)\n | where Workload == \"MicrosoftTeams\"\n | where Operation in (\"MemberAdded\", \"TeamsSessionStarted\")\n | extend Members = parse_json(tostring(AdditionalProperties.Members))\n | extend UPN = iif(Operation == \"MemberAdded\", tostring(Members[0].UPN), UserId)\n | extend Organization = tostring(split(split(UPN, \"_\")[1], \"#\")[0])\n | where isnotempty(Organization)\n | summarize by Organization\n);\n// EnrichedMicrosoft365AuditLogs Query for New Organizations\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between(starttime..endtime)\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"MemberAdded\"\n | extend Members = parse_json(tostring(AdditionalProperties.Members))\n | extend UPN = tostring(Members[0].UPN)\n | extend Organization = tostring(split(split(UPN, \"_\")[1], \"#\")[0])\n | where isnotempty(Organization)\n | where Organization !in (known_orgs_enriched)\n | extend AccountName = tostring(split(UPN, \"@\")[0]), AccountUPNSuffix = tostring(split(UPN, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeGenerated, *) by Organization, UPN;\n// Final Output\nCombinedEvents\n | project Organization, UPN, AccountName, AccountUPNSuffix\n | order by Organization asc\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "This query identifies external users added to Teams where the user's domain is not one previously seen in Teams data." + }, + { + "name": "tactics", + "value": "Persistence" + }, + { + "name": "techniques", + "value": "T1136" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject4')._huntingQuerycontentId4),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 4", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject4')._huntingQuerycontentId4)]", + "contentId": "[variables('huntingQueryObject4')._huntingQuerycontentId4]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject4').huntingQueryVersion4]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject4')._huntingQuerycontentId4]", + "contentKind": "HuntingQuery", + "displayName": "GSA Enriched Office 365 - External user from a new organisation added to Teams", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject4')._huntingQuerycontentId4,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject4')._huntingQuerycontentId4,'-', '2.0.2')))]", + "version": "2.0.2" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject5').huntingQueryTemplateSpecName5]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "Mail_redirect_via_ExO_transport_rule_hunting_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject5').huntingQueryVersion5]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_5", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule", + "category": "Hunting Queries", + "query": "// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload == \"Exchange\"\n | where Operation in (\"New-TransportRule\", \"Set-TransportRule\")\n | mv-apply DynamicParameters = todynamic(AdditionalProperties.Parameters) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | extend RuleName = case(\n Operation == \"Set-TransportRule\", ObjectId,\n Operation == \"New-TransportRule\", ParsedParameters.Name,\n \"Unknown\"\n )\n | mv-expand ExpandedParameters = todynamic(AdditionalProperties.Parameters)\n | where ExpandedParameters.Name in (\"BlindCopyTo\", \"RedirectMessageTo\") and isnotempty(ExpandedParameters.Value)\n | extend RedirectTo = ExpandedParameters.Value\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIp)[0]\n | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, Operation, RuleName, AdditionalProperties\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload == \"Exchange\"\n | where Operation in (\"New-TransportRule\", \"Set-TransportRule\")\n | mv-apply DynamicParameters = todynamic(Parameters) on (\n summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | extend RuleName = case(\n Operation == \"Set-TransportRule\", OfficeObjectId,\n Operation == \"New-TransportRule\", ParsedParameters.Name,\n \"Unknown\"\n )\n | mv-expand ExpandedParameters = todynamic(Parameters)\n | where ExpandedParameters.Name in (\"BlindCopyTo\", \"RedirectMessageTo\") and isnotempty(ExpandedParameters.Value)\n | extend RedirectTo = ExpandedParameters.Value\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIP)[0]\n | project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, Operation, RuleName, Parameters\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, IP_0_Address = IPAddress;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeGenerated, *) by RuleName, UserId;\n// Final Output\nCombinedEvents\n | project TimeGenerated, RuleName, RedirectTo, IPAddress, Port, UserId, AccountName, AccountUPNSuffix\n | order by TimeGenerated desc\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "Identifies when Exchange Online transport rule is configured to forward emails.\nThis could be an adversary mailbox configured to collect mail from multiple user accounts." + }, + { + "name": "tactics", + "value": "Collection,Exfiltration" + }, + { + "name": "techniques", + "value": "T1114,T1020" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject5')._huntingQuerycontentId5),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 5", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject5')._huntingQuerycontentId5)]", + "contentId": "[variables('huntingQueryObject5')._huntingQuerycontentId5]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject5').huntingQueryVersion5]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject5')._huntingQuerycontentId5]", + "contentKind": "HuntingQuery", + "displayName": "GSA Enriched Office 365 - Mail Redirect via ExO Transport Rule", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject5')._huntingQuerycontentId5,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject5')._huntingQuerycontentId5,'-', '2.0.2')))]", + "version": "2.0.2" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject6').huntingQueryTemplateSpecName6]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "MultipleTeamsDeletes_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject6').huntingQueryVersion6]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_6", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "GSA Enriched Office 365 - Multiple Teams deleted by a single user", + "category": "Hunting Queries", + "query": "let max_delete = 3; // Adjust this value to change how many Teams should be deleted before being included\n// EnrichedMicrosoft365AuditLogs - Users who deleted more than 'max_delete' Teams\nlet deleting_users_enriched = (\n EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"TeamDeleted\"\n | summarize count_ = count() by UserId\n | where count_ > max_delete\n | project UserId\n);\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"TeamDeleted\"\n | where UserId in (deleting_users_enriched)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix\n | project TimeGenerated, UserId, Account_0_Name, Account_0_UPNSuffix;\n// OfficeActivity - Users who deleted more than 'max_delete' Teams\nlet deleting_users_office = (\n OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"TeamDeleted\"\n | summarize count_ = count() by UserId\n | where count_ > max_delete\n | project UserId\n);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"TeamDeleted\"\n | where UserId in (deleting_users_office)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix\n | project TimeGenerated, UserId, Account_0_Name, Account_0_UPNSuffix;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(TimeGenerated, *) by UserId;\n// Final Output\nCombinedEvents\n| project TimeGenerated, UserId, Account_0_Name, Account_0_UPNSuffix\n| order by TimeGenerated desc\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "This hunting query identifies where multiple Teams have been deleted by a single user in a short timeframe." + }, + { + "name": "tactics", + "value": "Impact" + }, + { + "name": "techniques", + "value": "T1485,T1489" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject6')._huntingQuerycontentId6),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 6", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject6')._huntingQuerycontentId6)]", + "contentId": "[variables('huntingQueryObject6')._huntingQuerycontentId6]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject6').huntingQueryVersion6]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject6')._huntingQuerycontentId6]", + "contentKind": "HuntingQuery", + "displayName": "GSA Enriched Office 365 - Multiple Teams deleted by a single user", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject6')._huntingQuerycontentId6,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject6')._huntingQuerycontentId6,'-', '2.0.2')))]", + "version": "2.0.2" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject7').huntingQueryTemplateSpecName7]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "MultipleUsersEmailForwardedToSameDestination_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject7').huntingQueryVersion7]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_7", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination", + "category": "Hunting Queries", + "query": "let queryfrequency = 1d;\nlet queryperiod = 7d;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated > ago(queryperiod)\n | where Workload == \"Exchange\"\n | where AdditionalProperties has_any (\"ForwardTo\", \"RedirectTo\", \"ForwardingSmtpAddress\")\n | mv-apply DynamicParameters = todynamic(AdditionalProperties) on (\n summarize ParsedParameters = make_bag(bag_pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source')\n | extend DestinationMailAddress = tolower(case(\n isnotempty(column_ifexists(\"ForwardTo\", \"\")), column_ifexists(\"ForwardTo\", \"\"),\n isnotempty(column_ifexists(\"RedirectTo\", \"\")), column_ifexists(\"RedirectTo\", \"\"),\n isnotempty(column_ifexists(\"ForwardingSmtpAddress\", \"\")), trim_start(@\"smtp:\", column_ifexists(\"ForwardingSmtpAddress\", \"\")),\n \"\"\n ))\n | where isnotempty(DestinationMailAddress)\n | mv-expand split(DestinationMailAddress, \";\")\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIp)[0]\n | extend ClientIp = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1])\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIp\n | where DistinctUserCount > 1 and EndTime > ago(queryfrequency)\n | mv-expand UserId to typeof(string)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated > ago(queryperiod)\n | where OfficeWorkload =~ \"Exchange\"\n | where Parameters has_any (\"ForwardTo\", \"RedirectTo\", \"ForwardingSmtpAddress\")\n | mv-apply DynamicParameters = todynamic(Parameters) on (\n summarize ParsedParameters = make_bag(bag_pack(tostring(DynamicParameters.Name), DynamicParameters.Value))\n )\n | evaluate bag_unpack(ParsedParameters, columnsConflict='replace_source')\n | extend DestinationMailAddress = tolower(case(\n isnotempty(column_ifexists(\"ForwardTo\", \"\")), column_ifexists(\"ForwardTo\", \"\"),\n isnotempty(column_ifexists(\"RedirectTo\", \"\")), column_ifexists(\"RedirectTo\", \"\"),\n isnotempty(column_ifexists(\"ForwardingSmtpAddress\", \"\")), trim_start(@\"smtp:\", column_ifexists(\"ForwardingSmtpAddress\", \"\")),\n \"\"\n ))\n | where isnotempty(DestinationMailAddress)\n | mv-expand split(DestinationMailAddress, \";\")\n | extend ClientIPValues = extract_all(@'\\[?(::ffff:)?(?P(\\d+\\.\\d+\\.\\d+\\.\\d+)|[^\\]]+)\\]?([-:](?P\\d+))?', dynamic([\"IPAddress\", \"Port\"]), ClientIP)[0]\n | extend ClientIP = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1])\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DistinctUserCount = dcount(UserId), UserId = make_set(UserId, 250), Ports = make_set(Port, 250), EventCount = count() by tostring(DestinationMailAddress), ClientIP\n | where DistinctUserCount > 1 and EndTime > ago(queryfrequency)\n | mv-expand UserId to typeof(string)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, IP_0_Address = ClientIP;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by DestinationMailAddress, ClientIp;\n// Final Output\nCombinedEvents\n | project StartTime, EndTime, DestinationMailAddress, ClientIp, Ports, UserId, AccountName, AccountUPNSuffix\n | order by StartTime desc\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "Identifies when multiple (more than one) users' mailboxes are configured to forward to the same destination. \nThis could be an attacker-controlled destination mailbox configured to collect mail from multiple compromised user accounts." + }, + { + "name": "tactics", + "value": "Collection,Exfiltration" + }, + { + "name": "techniques", + "value": "T1114,T1020" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject7')._huntingQuerycontentId7),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 7", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject7')._huntingQuerycontentId7)]", + "contentId": "[variables('huntingQueryObject7')._huntingQuerycontentId7]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject7').huntingQueryVersion7]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject7')._huntingQuerycontentId7]", + "contentKind": "HuntingQuery", + "displayName": "GSA Enriched Office 365 - Multiple Users Email Forwarded to Same Destination", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject7')._huntingQuerycontentId7,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject7')._huntingQuerycontentId7,'-', '2.0.2')))]", + "version": "2.0.2" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject8').huntingQueryTemplateSpecName8]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "MultiTeamBot_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject8').huntingQueryVersion8]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_8", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "GSA Enriched Office 365 - Bots added to multiple teams", + "category": "Hunting Queries", + "query": "let threshold = 2; // Adjust this threshold based on your environment\nlet time_threshold = timespan(5m); // Adjust the time delta threshold\n// OfficeActivity Query\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"BotAddedToTeam\"\n | summarize Start = max(TimeGenerated), End = min(TimeGenerated), Teams = make_set(TeamName, 10000) by UserId\n | extend CountOfTeams = array_length(Teams)\n | extend TimeDelta = End - Start\n | where CountOfTeams > threshold\n | where TimeDelta >= time_threshold\n | project Start, End, Teams, CountOfTeams, UserId\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n// EnrichedMicrosoft365AuditLogs Query\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"BotAddedToTeam\"\n | extend TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n | summarize Start = max(TimeGenerated), End = min(TimeGenerated), Teams = make_set(TeamName, 10000) by UserId\n | extend CountOfTeams = array_length(Teams)\n | extend TimeDelta = End - Start\n | where CountOfTeams > threshold\n | where TimeDelta <= time_threshold\n | project Start, End, Teams, CountOfTeams, UserId\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(Start, *) by UserId;\n// Final Output\nCombinedEvents\n | project Start, End, Teams, CountOfTeams, UserId, AccountName, AccountUPNSuffix\n | order by Start desc\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "This hunting query helps identify bots added to multiple Teams in a short space of time." + }, + { + "name": "tactics", + "value": "Persistence,Collection" + }, + { + "name": "techniques", + "value": "T1176,T1119" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject8')._huntingQuerycontentId8),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 8", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject8')._huntingQuerycontentId8)]", + "contentId": "[variables('huntingQueryObject8')._huntingQuerycontentId8]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject8').huntingQueryVersion8]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject8')._huntingQuerycontentId8]", + "contentKind": "HuntingQuery", + "displayName": "GSA Enriched Office 365 - Bots added to multiple teams", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject8')._huntingQuerycontentId8,'-', '2.0.1')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject8')._huntingQuerycontentId8,'-', '2.0.1')))]", + "version": "2.0.1" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject9').huntingQueryTemplateSpecName9]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "MultiTeamOwner_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject9').huntingQueryVersion9]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_9", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "GSA Enriched Office 365 - User made Owner of multiple teams", + "category": "Hunting Queries", + "query": "let max_owner_count = 3; \n// Adjust this value to change how many teams a user is made owner of before detecting\n// OfficeActivity Query: Identify users who have been made owners of more than 'max_owner_count' teams\nlet high_owner_count_office = (\n OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberRoleChanged\"\n | extend Member = tostring(parse_json(Members)[0].UPN) \n | extend NewRole = toint(parse_json(Members)[0].Role)\n | where NewRole == 2 // Role 2 corresponds to \"Owner\"\n | summarize TeamCount = dcount(TeamName) by Member\n | where TeamCount > max_owner_count\n | project Member\n);\n// OfficeActivity Query: Fetch details for users with high ownership count\nlet OfficeEvents = OfficeActivity\n | where OfficeWorkload =~ \"MicrosoftTeams\"\n | where Operation =~ \"MemberRoleChanged\"\n | extend Member = tostring(parse_json(Members)[0].UPN)\n | extend NewRole = toint(parse_json(Members)[0].Role)\n | where NewRole == 2 // Role 2 corresponds to \"Owner\"\n | where Member in (high_owner_count_office)\n | extend AccountName = tostring(split(Member, \"@\")[0]), AccountUPNSuffix = tostring(split(Member, \"@\")[1]);\n// EnrichedMicrosoft365AuditLogs Query: Identify users who have been made owners of more than 'max_owner_count' teams\nlet high_owner_count_enriched = (\n EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"MemberRoleChanged\"\n | extend Member = tostring(UserId)\n | extend NewRole = toint(parse_json(tostring(AdditionalProperties)).Role)\n | where NewRole == 2 // Role 2 corresponds to \"Owner\"\n | summarize TeamCount = dcount(ObjectId) by Member\n | where TeamCount > max_owner_count\n | project Member\n);\n// EnrichedMicrosoft365AuditLogs Query: Fetch details for users with high ownership count\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"MemberRoleChanged\"\n | extend Member = tostring(UserId)\n | extend NewRole = toint(parse_json(tostring(AdditionalProperties)).Role)\n | where NewRole == 2 // Role 2 corresponds to \"Owner\"\n | where Member in (high_owner_count_enriched)\n | extend AccountName = tostring(split(Member, \"@\")[0]), AccountUPNSuffix = tostring(split(Member, \"@\")[1]);\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize by Member, AccountName, AccountUPNSuffix;\n// Final Output\nCombinedEvents\n| order by Member asc\n| project Member, AccountName, AccountUPNSuffix\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "This hunting query identifies users who have been made Owner of multiple Teams." + }, + { + "name": "tactics", + "value": "PrivilegeEscalation" + }, + { + "name": "techniques", + "value": "T1078" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject9')._huntingQuerycontentId9),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 9", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject9')._huntingQuerycontentId9)]", + "contentId": "[variables('huntingQueryObject9')._huntingQuerycontentId9]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject9').huntingQueryVersion9]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject9')._huntingQuerycontentId9]", + "contentKind": "HuntingQuery", + "displayName": "GSA Enriched Office 365 - User made Owner of multiple teams", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject9')._huntingQuerycontentId9,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject9')._huntingQuerycontentId9,'-', '2.0.2')))]", + "version": "2.0.2" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject10').huntingQueryTemplateSpecName10]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "NewBotAddedToTeams_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject10').huntingQueryVersion10]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_10", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "GSA Enriched Office 365 - Previously Unseen Bot or Application Added to Teams", + "category": "Hunting Queries", + "query": "let starttime = todatetime('{{StartTimeISO}}');\n let endtime = todatetime('{{EndTimeISO}}');\n let lookback = starttime - 14d;\n // Historical bots from EnrichedMicrosoft365AuditLogs\n let historical_bots_enriched = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (lookback .. starttime)\n | where Workload == \"MicrosoftTeams\"\n | extend AddonName = tostring(parse_json(tostring(AdditionalProperties)).AddonName)\n | where isnotempty(AddonName)\n | distinct AddonName;\n // Historical bots from OfficeActivity\n let historical_bots_office = OfficeActivity\n | where TimeGenerated between (lookback .. starttime)\n | where OfficeWorkload == \"MicrosoftTeams\"\n | where isnotempty(AddonName)\n | distinct AddonName;\n // Find new bots in Enriched Logs\n let new_bots_enriched = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where Workload == \"MicrosoftTeams\"\n | extend AddonName = tostring(parse_json(tostring(AdditionalProperties)).AddonName)\n | where AddonName !in (historical_bots_enriched)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n // Find new bots in OfficeActivity\n let new_bots_office = OfficeActivity\n | where TimeGenerated between (starttime .. endtime)\n | where OfficeWorkload == \"MicrosoftTeams\"\n | where isnotempty(AddonName)\n | where AddonName !in (historical_bots_office)\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1]);\n // Combine both new bots from Enriched Logs and OfficeActivity\n let CombinedNewBots = new_bots_enriched\n | union new_bots_office\n | summarize arg_min(TimeGenerated, *) by AddonName, UserId;\n // Final output\n CombinedNewBots\n | project TimeGenerated, AddonName, UserId, AccountName, AccountUPNSuffix\n | order by TimeGenerated desc\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "This hunting query helps identify new, and potentially unapproved applications or bots being added to Teams." + }, + { + "name": "tactics", + "value": "Persistence,Collection" + }, + { + "name": "techniques", + "value": "T1176,T1119" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject10')._huntingQuerycontentId10),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 10", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject10')._huntingQuerycontentId10)]", + "contentId": "[variables('huntingQueryObject10')._huntingQuerycontentId10]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject10').huntingQueryVersion10]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject10')._huntingQuerycontentId10]", + "contentKind": "HuntingQuery", + "displayName": "GSA Enriched Office 365 - Previously Unseen Bot or Application Added to Teams", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject10')._huntingQuerycontentId10,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject10')._huntingQuerycontentId10,'-', '2.0.2')))]", + "version": "2.0.2" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject11').huntingQueryTemplateSpecName11]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "new_adminaccountactivity_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject11').huntingQueryVersion11]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_11", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "New Admin Account Activity Seen Which Was Not Seen Historically", + "category": "Hunting Queries", + "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = starttime - 14d;\nlet historicalActivity =\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (lookback .. starttime)\n | where RecordType == \"ExchangeAdmin\" and UserType in (\"Admin\", \"DcAdmin\")\n | summarize historicalCount = count() by UserId;\nlet recentActivity = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where UserType in (\"Admin\", \"DcAdmin\")\n | summarize recentCount = count() by UserId;\nrecentActivity\n| join kind=leftanti (historicalActivity) on UserId\n| project UserId, recentCount\n| join kind=rightsemi (\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where RecordType == \"ExchangeAdmin\" \n | where UserType in (\"Admin\", \"DcAdmin\")\n ) on UserId\n| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by RecordType, Operation, UserType, UserId, ResultStatus\n| extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), UserId)\n| extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '')\n| extend AccountName = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[1]), AccountName)\n| extend AccountNTDomain = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[0]), '')\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "This will help you discover any new admin account activity which was seen and were not seen historically.\nAny new accounts seen in the results can be validated and investigated for any suspicious activities." + }, + { + "name": "tactics", + "value": "PrivilegeEscalation,Collection" + }, + { + "name": "techniques", + "value": "T1078,T1114" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject11')._huntingQuerycontentId11),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 11", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject11')._huntingQuerycontentId11)]", + "contentId": "[variables('huntingQueryObject11')._huntingQuerycontentId11]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject11').huntingQueryVersion11]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject11')._huntingQuerycontentId11]", + "contentKind": "HuntingQuery", + "displayName": "New Admin Account Activity Seen Which Was Not Seen Historically", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject11')._huntingQuerycontentId11,'-', '2.0.1')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject11')._huntingQuerycontentId11,'-', '2.0.1')))]", + "version": "2.0.1" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject12').huntingQueryTemplateSpecName12]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "new_sharepoint_downloads_by_IP_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject12').huntingQueryVersion12]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_12", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "GSA Enriched Office 365 - SharePointFileOperation via previously unseen IPs", + "category": "Hunting Queries", + "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = starttime - 14d;\nlet BLOCK_THRESHOLD = 1.0;\n// Identify Autonomous System Numbers (ASNs) with a high block rate in Sign-in Logs\nlet HighBlockRateASNs = SigninLogs\n | where TimeGenerated > lookback\n | where isnotempty(AutonomousSystemNumber)\n | summarize make_set(IPAddress), TotalIps = dcount(IPAddress), BlockedSignins = countif(ResultType == \"50053\"), TotalSignins = count() by AutonomousSystemNumber\n | extend BlockRatio = 1.00 * BlockedSignins / TotalSignins\n | where BlockRatio >= BLOCK_THRESHOLD\n | distinct AutonomousSystemNumber;\n// Retrieve IP addresses from these high block rate ASNs\nlet ASNIPs = SigninLogs\n | where TimeGenerated > lookback\n | where AutonomousSystemNumber in (HighBlockRateASNs)\n | distinct IPAddress, AutonomousSystemNumber;\n// OfficeActivity Query: File activities from identified ASN IPs\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated between(starttime .. endtime)\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | where ClientIP in (ASNIPs)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by ClientIP\n | extend IP_0_Address = ClientIP;\n// EnrichedMicrosoft365AuditLogs Query: File activities from identified ASN IPs\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | where ClientIp in (ASNIPs)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by ClientIp\n | extend IP_0_Address = ClientIp;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by ClientIP;\n// Final Output\nCombinedEvents\n | project StartTime, EndTime, RecentFileActivities, IP_0_Address\n | order by StartTime desc\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "Shows SharePoint upload/download volume by IPs with high-risk ASNs. New IPs with volume spikes may be unauthorized and exfiltrating documents." + }, + { + "name": "tactics", + "value": "Exfiltration" + }, + { + "name": "techniques", + "value": "T1030" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject12')._huntingQuerycontentId12),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 12", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject12')._huntingQuerycontentId12)]", + "contentId": "[variables('huntingQueryObject12')._huntingQuerycontentId12]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject12').huntingQueryVersion12]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject12')._huntingQuerycontentId12]", + "contentKind": "HuntingQuery", + "displayName": "GSA Enriched Office 365 - SharePointFileOperation via previously unseen IPs", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject12')._huntingQuerycontentId12,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject12')._huntingQuerycontentId12,'-', '2.0.2')))]", + "version": "2.0.2" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject13').huntingQueryTemplateSpecName13]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "new_sharepoint_downloads_by_UserAgent_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject13').huntingQueryVersion13]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_13", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "GSA Enriched Office 365 -SharePointFileOperation via devices with previously unseen user agents", + "category": "Hunting Queries", + "query": "let starttime = todatetime('{{StartTimeISO}}');\nlet endtime = todatetime('{{EndTimeISO}}');\nlet lookback = starttime - 14d;\nlet MINIMUM_BLOCKS = 10;\nlet SUCCESS_THRESHOLD = 0.2;\n// Identify user agents or client apps with a low success-to-block ratio in Sign-in Logs\nlet HistoricalActivity = SigninLogs\n | where TimeGenerated > lookback\n | where isnotempty(UserAgent)\n | summarize SuccessfulSignins = countif(ResultType == \"0\"), BlockedSignins = countif(ResultType == \"50053\") by UserAgent\n | extend SuccessBlockRatio = 1.00 * SuccessfulSignins / BlockedSignins\n | where SuccessBlockRatio < SUCCESS_THRESHOLD\n | where BlockedSignins > MINIMUM_BLOCKS;\n// OfficeActivity Query: File operations by matching user agents\nlet OfficeEvents = OfficeActivity\n | where TimeGenerated between (starttime .. endtime)\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by UserAgent, UserId, ClientIP, Site_Url\n | join kind=innerunique (HistoricalActivity) on UserAgent\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIP, Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, URL_0_Url = Site_Url;\n// EnrichedMicrosoft365AuditLogs Query: File operations by matching client apps (UserAgent)\nlet EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent) // Ensure matching with UserAgent column\n | extend SiteUrl = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), RecentFileActivities = count() by UserAgent, UserId, ClientIp, SiteUrl\n | join kind=innerunique (HistoricalActivity) on UserAgent\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIp, Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, URL_0_Url = SiteUrl;\n// Combine Office and Enriched Logs\nlet CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by UserId, ClientIP;\n// Final Output\nCombinedEvents\n | project StartTime, EndTime, RecentFileActivities, IP_0_Address, Account_0_Name, Account_0_UPNSuffix, URL_0_Url\n | order by StartTime desc;\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "Tracking via user agent is one way to differentiate between types of connecting device.\nIn homogeneous enterprise environments the user agent associated with an attacker device may stand out as unusual." + }, + { + "name": "tactics", + "value": "Exfiltration" + }, + { + "name": "techniques", + "value": "T1030" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject13')._huntingQuerycontentId13),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 13", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject13')._huntingQuerycontentId13)]", + "contentId": "[variables('huntingQueryObject13')._huntingQuerycontentId13]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject13').huntingQueryVersion13]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject13')._huntingQuerycontentId13]", + "contentKind": "HuntingQuery", + "displayName": "GSA Enriched Office 365 -SharePointFileOperation via devices with previously unseen user agents", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject13')._huntingQuerycontentId13,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject13')._huntingQuerycontentId13,'-', '2.0.2')))]", + "version": "2.0.2" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject14').huntingQueryTemplateSpecName14]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "New_WindowsReservedFileNamesOnOfficeFileServices_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject14').huntingQueryVersion14]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_14", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "GSA Enriched Office 365 - New Windows Reserved Filenames staged on Office file services", + "category": "Hunting Queries", + "query": "let starttime = todatetime('{{StartTimeISO}}');\n let endtime = todatetime('{{EndTimeISO}}');\n let lookback = totimespan((endtime - starttime) * 7);\n \n // Reserved file names and extensions for Windows\n let Reserved = dynamic(['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']);\n \n // EnrichedMicrosoft365AuditLogs Query\n let EnrichedEvents = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | extend FileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | extend ClientUserAgent = tostring(parse_json(tostring(AdditionalProperties)).ClientUserAgent)\n | extend SiteUrl = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(ObjectId)\n | where ObjectId !~ FileName\n | where ObjectId in (Reserved) or FileName in (Reserved)\n | where ClientUserAgent !has \"Mac OS\"\n | join kind=leftanti (\n EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (ago(lookback) .. starttime)\n | extend FileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | extend ClientUserAgent = tostring(parse_json(tostring(AdditionalProperties)).ClientUserAgent)\n | extend SiteUrl = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(ObjectId)\n | where ObjectId !~ FileName\n | where ObjectId in (Reserved) or FileName in (Reserved)\n | where ClientUserAgent !has \"Mac OS\"\n | summarize PrevSeenCount = count() by ObjectId, UserId, FileName\n ) on ObjectId\n | extend SiteUrlUserFolder = tolower(split(SiteUrl, '/')[-2])\n | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n | extend UserIdDiffThanUserFolder = iff(SiteUrl has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(ClientUserAgent, 100000), Ids = make_list(Id, 100000),\n SourceRelativeUrls = make_list(ObjectId, 100000), FileNames = make_list(FileName, 100000) by Workload, RecordType, UserType, UserKey, UserId, ClientIp, SiteUrl, ObjectId, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIp\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, URL_0_Url = SiteUrl;\n // OfficeActivity Query\n let OfficeEvents = OfficeActivity\n | where TimeGenerated between (starttime .. endtime)\n | where isnotempty(SourceFileExtension)\n | where SourceFileName !~ SourceFileExtension\n | where SourceFileExtension in (Reserved) or SourceFileName in (Reserved)\n | where UserAgent !has \"Mac OS\"\n | join kind=leftanti (\n OfficeActivity\n | where TimeGenerated between (ago(lookback) .. starttime)\n | where isnotempty(SourceFileExtension)\n | where SourceFileName !~ SourceFileExtension\n | where SourceFileExtension in (Reserved) or SourceFileName in (Reserved)\n | where UserAgent !has \"Mac OS\"\n | summarize PrevSeenCount = count() by SourceFileExtension\n ) on SourceFileExtension\n | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])\n | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(UserAgent, 100000), OfficeIds = make_list(OfficeId, 100000),\n SourceRelativeUrls = make_list(SourceRelativeUrl, 100000), FileNames = make_list(SourceFileName, 100000) by OfficeWorkload, RecordType, UserType, UserKey, UserId, ClientIP, Site_Url, SourceFileExtension, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIP\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix, URL_0_Url = Site_Url;\n // Combine Office and Enriched Logs\n let CombinedEvents = OfficeEvents\n | union EnrichedEvents\n | summarize arg_min(StartTime, *) by UserId, ClientIP;\n // Final Output\n CombinedEvents\n | project StartTime, EndTime, Operations, UserAgents, IP_0_Address, Account_0_Name, Account_0_UPNSuffix, URL_0_Url\n | order by StartTime desc\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "This identifies new Windows Reserved Filenames on Office services like SharePoint and OneDrive in the past 7 days. It also detects when a user uploads these files to another user's workspace, which may indicate malicious activity." + }, + { + "name": "tactics", + "value": "CommandAndControl" + }, + { + "name": "techniques", + "value": "T1105" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject14')._huntingQuerycontentId14),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 14", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject14')._huntingQuerycontentId14)]", + "contentId": "[variables('huntingQueryObject14')._huntingQuerycontentId14]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject14').huntingQueryVersion14]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject14')._huntingQuerycontentId14]", + "contentKind": "HuntingQuery", + "displayName": "GSA Enriched Office 365 - New Windows Reserved Filenames staged on Office file services", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject14')._huntingQuerycontentId14,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject14')._huntingQuerycontentId14,'-', '2.0.2')))]", + "version": "2.0.2" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject15').huntingQueryTemplateSpecName15]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "nonowner_MailboxLogin_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject15').huntingQueryVersion15]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_15", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "GSA Enriched Office 365 - Non-owner mailbox login activity", + "category": "Hunting Queries", + "query": "let starttime = todatetime('{{StartTimeISO}}');\n let endtime = todatetime('{{EndTimeISO}}');\n // Enriched Logs Query for Mailbox Logins (non-owner)\n let EnrichedMailboxLogins = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where Workload == \"Exchange\"\n | where Operation == \"MailboxLogin\"\n | extend Logon_Type = tostring(parse_json(tostring(AdditionalProperties)).LogonType)\n | extend MailboxOwnerUPN = tostring(parse_json(tostring(AdditionalProperties)).MailboxOwnerUPN)\n | where Logon_Type != \"Owner\"\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by Operation, UserType, UserId, MailboxOwnerUPN, Logon_Type, ClientIp\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIp\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n // Office Activity Query for Mailbox Logins (non-owner)\n let OfficeMailboxLogins = OfficeActivity\n | where TimeGenerated between (starttime .. endtime)\n | where OfficeWorkload == \"Exchange\"\n | where Operation == \"MailboxLogin\" and Logon_Type != \"Owner\"\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), count() by Operation, OrganizationName, UserType, UserId, MailboxOwnerUPN, Logon_Type, ClientIP\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIP\n | extend Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n // Combine both results\n let CombinedMailboxLogins = EnrichedMailboxLogins\n | union OfficeMailboxLogins\n | summarize arg_min(StartTime, *) by UserId, MailboxOwnerUPN, Logon_Type;\n // Final output\n CombinedMailboxLogins\n | project StartTime, EndTime, Operation, UserId, MailboxOwnerUPN, Logon_Type, Account_0_Name, Account_0_UPNSuffix, IP_0_Address\n | order by StartTime desc\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "Finds non-owner mailbox access by admin/delegate permissions. Whitelist valid users and check others for unauthorized access." + }, + { + "name": "tactics", + "value": "Collection,Exfiltration" + }, + { + "name": "techniques", + "value": "T1114,T1020" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject15')._huntingQuerycontentId15),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 15", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject15')._huntingQuerycontentId15)]", + "contentId": "[variables('huntingQueryObject15')._huntingQuerycontentId15]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject15').huntingQueryVersion15]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject15')._huntingQuerycontentId15]", + "contentKind": "HuntingQuery", + "displayName": "GSA Enriched Office 365 - Non-owner mailbox login activity", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject15')._huntingQuerycontentId15,'-', '2.0.1')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject15')._huntingQuerycontentId15,'-', '2.0.1')))]", + "version": "2.0.1" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject16').huntingQueryTemplateSpecName16]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "OfficeMailForwarding_hunting_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject16').huntingQueryVersion16]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_16", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "GSA Enriched Office 365 - Office Mail Forwarding - Hunting Version", + "category": "Hunting Queries", + "query": "let starttime = todatetime('{{StartTimeISO}}');\n let endtime = todatetime('{{EndTimeISO}}');\n \n // Enriched Logs Query for forwarding rule operations\n let EnrichedForwardRules = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where Workload == \"Exchange\"\n | where (Operation == \"Set-Mailbox\" and tostring(parse_json(tostring(AdditionalProperties))) contains 'ForwardingSmtpAddress') \n or (Operation in ('New-InboxRule', 'Set-InboxRule') and (tostring(parse_json(tostring(AdditionalProperties))) contains 'ForwardTo' or tostring(parse_json(tostring(AdditionalProperties))) contains 'RedirectTo'))\n | extend parsed = parse_json(tostring(AdditionalProperties))\n | extend fwdingDestination_initial = iif(Operation == \"Set-Mailbox\", tostring(parsed.ForwardingSmtpAddress), coalesce(tostring(parsed.ForwardTo), tostring(parsed.RedirectTo)))\n | where isnotempty(fwdingDestination_initial)\n | extend fwdingDestination = iff(fwdingDestination_initial has \"smtp\", (split(fwdingDestination_initial, \":\")[1]), fwdingDestination_initial)\n | parse fwdingDestination with * '@' ForwardedtoDomain \n | parse UserId with * '@' UserDomain\n | extend subDomain = ((split(strcat(tostring(split(UserDomain, '.')[-2]), '.', tostring(split(UserDomain, '.')[-1])), '.'))[0])\n | where ForwardedtoDomain !contains subDomain\n | extend Result = iff(ForwardedtoDomain != UserDomain, \"Mailbox rule created to forward to External Domain\", \"Forward rule for Internal domain\")\n | extend ClientIPAddress = case(ClientIp has \".\", tostring(split(ClientIp, \":\")[0]), ClientIp has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIp, \"]\")[0]))), ClientIp)\n | extend Port = case(ClientIp has \".\", (split(ClientIp, \":\")[1]), ClientIp has \"[\", tostring(split(ClientIp, \"]:\")[1]), ClientIp)\n | extend Host = tostring(parse_json(tostring(AdditionalProperties)).OriginatingServer)\n | extend HostName = tostring(split(Host, \".\")[0])\n | extend DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))\n | project TimeGenerated, UserId, UserDomain, subDomain, Operation, ForwardedtoDomain, ClientIPAddress, Result, Port, ObjectId, fwdingDestination, HostName, DnsDomain\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIPAddress, Host_0_HostName = HostName, Host_0_DnsDomain = DnsDomain, Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n // Office Activity Query for forwarding rule operations\n let OfficeForwardRules = OfficeActivity\n | where TimeGenerated between (starttime .. endtime)\n | where OfficeWorkload == \"Exchange\"\n | where (Operation =~ \"Set-Mailbox\" and Parameters contains 'ForwardingSmtpAddress') \n or (Operation in~ ('New-InboxRule', 'Set-InboxRule') and (Parameters contains 'ForwardTo' or Parameters contains 'RedirectTo'))\n | extend parsed = parse_json(Parameters)\n | extend fwdingDestination_initial = (iif(Operation =~ \"Set-Mailbox\", tostring(parsed[1].Value), tostring(parsed[2].Value)))\n | where isnotempty(fwdingDestination_initial)\n | extend fwdingDestination = iff(fwdingDestination_initial has \"smtp\", (split(fwdingDestination_initial, \":\")[1]), fwdingDestination_initial)\n | parse fwdingDestination with * '@' ForwardedtoDomain \n | parse UserId with * '@' UserDomain\n | extend subDomain = ((split(strcat(tostring(split(UserDomain, '.')[-2]), '.', tostring(split(UserDomain, '.')[-1])), '.') [0]))\n | where ForwardedtoDomain !contains subDomain\n | extend Result = iff(ForwardedtoDomain != UserDomain, \"Mailbox rule created to forward to External Domain\", \"Forward rule for Internal domain\")\n | extend ClientIPAddress = case(ClientIP has \".\", tostring(split(ClientIP, \":\")[0]), ClientIP has \"[\", tostring(trim_start(@'[[]', tostring(split(ClientIP, \"]\")[0]))), ClientIP)\n | extend Port = case(ClientIP has \".\", (split(ClientIP, \":\")[1]), ClientIP has \"[\", tostring(split(ClientIP, \"]:\")[1]), ClientIP)\n | extend Host = tostring(split(OriginatingServer, \" (\")[0])\n | extend HostName = tostring(split(Host, \".\")[0])\n | extend DnsDomain = tostring(strcat_array(array_slice(split(Host, '.'), 1, -1), '.'))\n | project TimeGenerated, UserId, UserDomain, subDomain, Operation, ForwardedtoDomain, ClientIPAddress, Result, Port, HostName, DnsDomain\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIPAddress, Host_0_HostName = HostName, Host_0_DnsDomain = DnsDomain, Account_0_Name = AccountName, Account_0_UPNSuffix = AccountUPNSuffix;\n // Combine the results from both Enriched and Office Activity logs\n let CombinedForwardRules = EnrichedForwardRules\n | union OfficeForwardRules\n | summarize arg_min(TimeGenerated, *) by UserId, ForwardedtoDomain, Operation\n | project TimeGenerated, UserId, UserDomain, subDomain, Operation, ForwardedtoDomain, ClientIPAddress, Result, Port, ObjectId, fwdingDestination, Host_0_HostName, Host_0_DnsDomain, IP_0_Address, Account_0_Name, Account_0_UPNSuffix; \n // Final output\n CombinedForwardRules\n | order by TimeGenerated desc;\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "Adversaries often abuse email-forwarding rules to monitor victim activities, steal information, and gain intelligence on the victim or their organization. This query highlights cases where user mail is being forwarded, including to external domains." + }, + { + "name": "tactics", + "value": "Collection,Exfiltration" + }, + { + "name": "techniques", + "value": "T1114,T1020" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject16')._huntingQuerycontentId16),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 16", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject16')._huntingQuerycontentId16)]", + "contentId": "[variables('huntingQueryObject16')._huntingQuerycontentId16]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject16').huntingQueryVersion16]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject16')._huntingQuerycontentId16]", + "contentKind": "HuntingQuery", + "displayName": "GSA Enriched Office 365 - Office Mail Forwarding - Hunting Version", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject16')._huntingQuerycontentId16,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject16')._huntingQuerycontentId16,'-', '2.0.2')))]", + "version": "2.0.2" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject17').huntingQueryTemplateSpecName17]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "powershell_or_nonbrowser_MailboxLogin_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject17').huntingQueryVersion17]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_17", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "GSA Enriched Office 365 - PowerShell or non-browser mailbox login activity", + "category": "Hunting Queries", + "query": "let starttime = todatetime('{{StartTimeISO}}');\n let endtime = todatetime('{{EndTimeISO}}');\n // EnrichedMicrosoft365AuditLogs query\n let EnrichedMailboxLogin = EnrichedMicrosoft365AuditLogs\n | where TimeGenerated between (starttime .. endtime)\n | where Workload == \"Exchange\" and Operation == \"MailboxLogin\"\n | extend ClientApplication = tostring(parse_json(AdditionalProperties).ClientInfoString)\n | where ClientApplication == \"Client=Microsoft.Exchange.Powershell; Microsoft WinRM Client\"\n | extend TenantName = tostring(parse_json(AdditionalProperties).TenantName)\n | extend MailboxOwner = tostring(parse_json(AdditionalProperties).MailboxOwnerUPN)\n | extend LogonType = tostring(parse_json(AdditionalProperties).LogonType)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Count = count() by Operation, TenantName, UserType, UserId, MailboxOwner, LogonType, ClientApplication\n | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), UserId)\n | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '')\n | extend AccountName = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[1]), AccountName)\n | extend AccountNTDomain = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[0]), '');\n // OfficeActivity query\n let OfficeMailboxLogin = OfficeActivity\n | where TimeGenerated between (starttime .. endtime)\n | where OfficeWorkload == \"Exchange\" and Operation == \"MailboxLogin\"\n | where ClientInfoString == \"Client=Microsoft.Exchange.Powershell; Microsoft WinRM Client\"\n | extend LogonType = \"Unknown\" // If LogonType does not exist, create a placeholder\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Count = count() by Operation, OrganizationName, UserType, UserId, MailboxOwnerUPN, LogonType, ClientInfoString\n | extend AccountName = iff(UserId contains '@', tostring(split(UserId, '@')[0]), UserId)\n | extend AccountUPNSuffix = iff(UserId contains '@', tostring(split(UserId, '@')[1]), '')\n | extend AccountName = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[1]), AccountName)\n | extend AccountNTDomain = iff(UserId contains '\\\\', tostring(split(UserId, '\\\\')[0]), '');\n // Combine Enriched and Office queries\n let CombinedMailboxLogin = EnrichedMailboxLogin\n | union OfficeMailboxLogin\n | summarize arg_min(StartTime, *) by UserId, Operation\n | project StartTime, EndTime, Operation, TenantName, OrganizationName, UserType, UserId, MailboxOwner, LogonType, ClientApplication, ClientInfoString, Count, AccountName, AccountUPNSuffix, AccountNTDomain;\n // Final output\n CombinedMailboxLogin\n | order by StartTime desc;\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "Detects mailbox login from Exchange PowerShell. All accounts can use it by default, but admins can change it. Whitelist benign activities." + }, + { + "name": "tactics", + "value": "Execution,Persistence,Collection" + }, + { + "name": "techniques", + "value": "T1059,T1098,T1114" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject17')._huntingQuerycontentId17),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 17", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject17')._huntingQuerycontentId17)]", + "contentId": "[variables('huntingQueryObject17')._huntingQuerycontentId17]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject17').huntingQueryVersion17]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject17')._huntingQuerycontentId17]", + "contentKind": "HuntingQuery", + "displayName": "GSA Enriched Office 365 - PowerShell or non-browser mailbox login activity", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject17')._huntingQuerycontentId17,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject17')._huntingQuerycontentId17,'-', '2.0.2')))]", + "version": "2.0.2" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject18').huntingQueryTemplateSpecName18]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "sharepoint_downloads_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject18').huntingQueryVersion18]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_18", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "GSA Enriched Office 365 - SharePoint File Operation via Client IP with Previously Unseen User Agents", + "category": "Hunting Queries", + "query": "let starttime = todatetime('{{StartTimeISO}}');\n let endtime = todatetime('{{EndTimeISO}}');\n let lookback = starttime - 14d;\n // Historical user agents in EnrichedMicrosoft365AuditLogs\n let historicalUA_Enriched = EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | where TimeGenerated between (lookback .. starttime)\n | extend ClientApplication = tostring(parse_json(AdditionalProperties).UserAgent)\n | summarize by ClientIp, ClientApplication;\n // Recent user agents in EnrichedMicrosoft365AuditLogs\n let recentUA_Enriched = EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | where TimeGenerated between (starttime .. endtime)\n | extend ClientApplication = tostring(parse_json(AdditionalProperties).UserAgent)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by ClientIp, ClientApplication;\n // Combine historical and recent user agents from EnrichedMicrosoft365AuditLogs\n let Enriched_UA = recentUA_Enriched\n | join kind=leftanti (historicalUA_Enriched) on ClientIp, ClientApplication\n | where not(isempty(ClientIp))\n | extend IP_0_Address = ClientIp;\n // Historical user agents in OfficeActivity\n let historicalUA_Office = OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | where TimeGenerated between (lookback .. starttime)\n | summarize by ClientIP, UserAgent;\n // Recent user agents in OfficeActivity\n let recentUA_Office = OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileUploaded\")\n | where TimeGenerated between (starttime .. endtime)\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated) by ClientIP, UserAgent;\n // Combine historical and recent user agents from OfficeActivity\n let Office_UA = recentUA_Office\n | join kind=leftanti (historicalUA_Office) on ClientIP, UserAgent\n | where not(isempty(ClientIP))\n | extend IP_0_Address = ClientIP;\n // Final combined result\n Enriched_UA\n | union Office_UA\n | project StartTime, EndTime, ClientIp, ClientApplication, IP_0_Address;\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "New user agents associated with a client IP for SharePoint file uploads/downloads." + }, + { + "name": "tactics", + "value": "Exfiltration" + }, + { + "name": "techniques", + "value": "T1030" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject18')._huntingQuerycontentId18),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 18", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject18')._huntingQuerycontentId18)]", + "contentId": "[variables('huntingQueryObject18')._huntingQuerycontentId18]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject18').huntingQueryVersion18]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject18')._huntingQuerycontentId18]", + "contentKind": "HuntingQuery", + "displayName": "GSA Enriched Office 365 - SharePoint File Operation via Client IP with Previously Unseen User Agents", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject18')._huntingQuerycontentId18,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject18')._huntingQuerycontentId18,'-', '2.0.2')))]", + "version": "2.0.2" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject19').huntingQueryTemplateSpecName19]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "TeamsFilesUploaded_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject19').huntingQueryVersion19]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_19", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "GSA Enriched Office 365 - Files uploaded to teams and access summary", + "category": "Hunting Queries", + "query": "// Define the query for EnrichedMicrosoft365AuditLogs\nlet enrichedLogs = EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation == \"FileUploaded\"\n | where UserId != \"app@sharepoint\"\n | where ObjectId has \"Microsoft Teams Chat Files\"\n | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | join kind=leftouter (\n EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileAccessed\")\n | where UserId != \"app@sharepoint\"\n | where ObjectId has \"Microsoft Teams Chat Files\"\n | extend UserId1 = UserId, ClientIp1 = ClientIp\n ) on ObjectId\n | extend userBag = bag_pack(\"UserId1\", UserId1, \"ClientIp1\", ClientIp1)\n | summarize AccessedBy = make_bag(userBag), make_set(UserId1, 10000) by bin(TimeGenerated, 1h), UserId, ObjectId, SourceFileName\n | extend NumberOfUsersAccessed = array_length(bag_keys(AccessedBy))\n | project timestamp = TimeGenerated, UserId, FileLocation = ObjectId, FileName = SourceFileName, AccessedBy, NumberOfUsersAccessed\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix;\n// Define the query for OfficeActivity\nlet officeLogs = OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where Operation == \"FileUploaded\"\n | where UserId != \"app@sharepoint\"\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | join kind=leftouter (\n OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where Operation in (\"FileDownloaded\", \"FileAccessed\")\n | where UserId != \"app@sharepoint\"\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n ) on OfficeObjectId\n | extend userBag = bag_pack(UserId1, ClientIP1)\n | summarize AccessedBy = make_bag(userBag, 10000), make_set(UserId1, 10000) by TimeGenerated, UserId, OfficeObjectId, SourceFileName\n | extend NumberUsers = array_length(bag_keys(AccessedBy))\n | project timestamp = TimeGenerated, UserId, FileLocation = OfficeObjectId, FileName = SourceFileName, AccessedBy, NumberOfUsersAccessed = NumberUsers\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix;\n// Union both results\nenrichedLogs\n| union officeLogs\n| order by timestamp desc;\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "This hunting query identifies files uploaded to SharePoint via a Teams chat and\nsummarizes users and IP addresses that have accessed these files. This allows for \nidentification of anomalous file sharing patterns." + }, + { + "name": "tactics", + "value": "InitialAccess,Exfiltration" + }, + { + "name": "techniques", + "value": "T1199,T1102,T1078" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject19')._huntingQuerycontentId19),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 19", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject19')._huntingQuerycontentId19)]", + "contentId": "[variables('huntingQueryObject19')._huntingQuerycontentId19]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject19').huntingQueryVersion19]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject19')._huntingQuerycontentId19]", + "contentKind": "HuntingQuery", + "displayName": "GSA Enriched Office 365 - Files uploaded to teams and access summary", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject19')._huntingQuerycontentId19,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject19')._huntingQuerycontentId19,'-', '2.0.2')))]", + "version": "2.0.2" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject20').huntingQueryTemplateSpecName20]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "UserAddToTeamsAndUploadsFile_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject20').huntingQueryVersion20]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_20", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "GSA Enriched Office 365 - User added to Teams and immediately uploads file", + "category": "Hunting Queries", + "query": "let threshold = 1m;\n // Define MemberAddedEvents for EnrichedMicrosoft365AuditLogs\n let MemberAddedEvents_Enriched = EnrichedMicrosoft365AuditLogs\n | where Workload == \"MicrosoftTeams\"\n | where Operation == \"MemberAdded\"\n | extend TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)\n | project TimeGenerated, UploaderID = UserId, TeamName;\n // Define FileUploadEvents for EnrichedMicrosoft365AuditLogs\n let FileUploadEvents_Enriched = EnrichedMicrosoft365AuditLogs\n | where RecordType == \"SharePointFileOperation\"\n | where ObjectId has \"Microsoft Teams Chat Files\"\n | where Operation == \"FileUploaded\"\n | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | project UploadTime = TimeGenerated, UploaderID = UserId, FileLocation = ObjectId, SourceFileName;\n // Perform join for EnrichedMicrosoft365AuditLogs\n let EnrichedResults = MemberAddedEvents_Enriched\n | join kind=inner (FileUploadEvents_Enriched) on UploaderID\n | where UploadTime > TimeGenerated and UploadTime < TimeGenerated + threshold\n | extend timestamp = TimeGenerated, AccountCustomEntity = UploaderID;\n // Define MemberAddedEvents for OfficeActivity\n let MemberAddedEvents_Office = OfficeActivity\n | where OfficeWorkload == \"MicrosoftTeams\"\n | where Operation == \"MemberAdded\"\n | extend TeamName = iff(isempty(TeamName), Members[0].UPN, TeamName)\n | project TimeGenerated, UploaderID = UserId, TeamName;\n // Define FileUploadEvents for OfficeActivity\n let FileUploadEvents_Office = OfficeActivity\n | where RecordType == \"SharePointFileOperation\"\n | where SourceRelativeUrl has \"Microsoft Teams Chat Files\"\n | where Operation == \"FileUploaded\"\n | project UploadTime = TimeGenerated, UploaderID = UserId, FileLocation = OfficeObjectId, FileName = SourceFileName;\n // Perform join for OfficeActivity\n let OfficeResults = MemberAddedEvents_Office\n | join kind=inner (FileUploadEvents_Office) on UploaderID\n | where UploadTime > TimeGenerated and UploadTime < TimeGenerated + threshold\n | project-away UploaderID1\n | extend timestamp = TimeGenerated, AccountCustomEntity = UploaderID;\n // Union both results\n EnrichedResults\n | union OfficeResults\n | order by timestamp desc;\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "This hunting query identifies users who are added to a Teams Channel or Teams chat\nand within 1 minute of being added upload a file via the chat. This might be\nan indicator of suspicious activity." + }, + { + "name": "tactics", + "value": "InitialAccess" + }, + { + "name": "techniques", + "value": "T1566" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject20')._huntingQuerycontentId20),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 20", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject20')._huntingQuerycontentId20)]", + "contentId": "[variables('huntingQueryObject20')._huntingQuerycontentId20]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject20').huntingQueryVersion20]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject20')._huntingQuerycontentId20]", + "contentKind": "HuntingQuery", + "displayName": "GSA Enriched Office 365 - User added to Teams and immediately uploads file", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject20')._huntingQuerycontentId20,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject20')._huntingQuerycontentId20,'-', '2.0.2')))]", + "version": "2.0.2" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('huntingQueryObject21').huntingQueryTemplateSpecName21]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "WindowsReservedFileNamesOnOfficeFileServices_HuntingQueries Hunting Query with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('huntingQueryObject21').huntingQueryVersion21]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.OperationalInsights/savedSearches", + "apiVersion": "2022-10-01", + "name": "Global_Secure_Access_Hunting_Query_21", + "location": "[parameters('workspace-location')]", + "properties": { + "eTag": "*", + "displayName": "GSA Enriched Office 365 - Windows Reserved Filenames Staged on Office File Services", + "category": "Hunting Queries", + "query": "let Reserved = dynamic(['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9']);\n // Query for OfficeActivity\n let OfficeActivityResults = OfficeActivity\n | where isnotempty(SourceFileExtension)\n | where SourceFileExtension in (Reserved) or SourceFileName in (Reserved)\n | where UserAgent !has \"Mac OS\"\n | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])\n | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(UserAgent, 100000), OfficeIds = make_list(OfficeId, 100000), SourceRelativeUrls = make_list(SourceRelativeUrl, 100000), FileNames = make_list(SourceFileName, 100000)\n by OfficeWorkload, RecordType, UserType, UserKey, UserId, ClientIP, Site_Url, SourceFileExtension, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIP\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix\n | extend URL_0_Url = Site_Url;\n // Query for EnrichedMicrosoft365AuditLogs\n let EnrichedMicrosoft365Results = EnrichedMicrosoft365AuditLogs\n | extend SourceFileName = tostring(parse_json(tostring(AdditionalProperties)).SourceFileName)\n | extend UserAgent = tostring(parse_json(tostring(AdditionalProperties)).UserAgent)\n | extend Site_Url = tostring(parse_json(tostring(AdditionalProperties)).SiteUrl)\n | where isnotempty(ObjectId)\n | where ObjectId in (Reserved) or SourceFileName in (Reserved)\n | where UserAgent !has \"Mac OS\"\n | extend SiteUrlUserFolder = tolower(split(Site_Url, '/')[-2])\n | extend UserIdUserFolderFormat = tolower(replace_regex(UserId, '@|\\\\.', '_'))\n | extend UserIdDiffThanUserFolder = iff(Site_Url has '/personal/' and SiteUrlUserFolder != UserIdUserFolderFormat, true, false)\n | summarize TimeGenerated = make_list(TimeGenerated, 100000), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), Operations = make_list(Operation, 100000), UserAgents = make_list(UserAgent, 100000), ObjectIds = make_list(Id, 100000), SourceRelativeUrls = make_list(ObjectId, 100000), FileNames = make_list(SourceFileName, 100000)\n by Workload, RecordType, UserType, UserKey, UserId, ClientIp, Site_Url, ObjectId, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder\n | extend AccountName = tostring(split(UserId, \"@\")[0]), AccountUPNSuffix = tostring(split(UserId, \"@\")[1])\n | extend IP_0_Address = ClientIp\n | extend Account_0_Name = AccountName\n | extend Account_0_UPNSuffix = AccountUPNSuffix\n | extend URL_0_Url = Site_Url;\n // Combine both queries\n OfficeActivityResults\n | union EnrichedMicrosoft365Results\n | order by StartTime desc;\n", + "version": 2, + "tags": [ + { + "name": "description", + "value": "This identifies Windows Reserved Filenames on Office services like SharePoint and OneDrive. It also detects when a user uploads these files to another user's workspace, which may indicate malicious activity." + }, + { + "name": "tactics", + "value": "CommandAndControl" + }, + { + "name": "techniques", + "value": "T1105" + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('HuntingQuery-', last(split(resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject21')._huntingQuerycontentId21),'/'))))]", + "properties": { + "description": "Global Secure Access Hunting Query 21", + "parentId": "[resourceId('Microsoft.OperationalInsights/savedSearches', variables('huntingQueryObject21')._huntingQuerycontentId21)]", + "contentId": "[variables('huntingQueryObject21')._huntingQuerycontentId21]", + "kind": "HuntingQuery", + "version": "[variables('huntingQueryObject21').huntingQueryVersion21]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "tier": "Partner", + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('huntingQueryObject21')._huntingQuerycontentId21]", + "contentKind": "HuntingQuery", + "displayName": "GSA Enriched Office 365 - Windows Reserved Filenames Staged on Office File Services", + "contentProductId": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject21')._huntingQuerycontentId21,'-', '2.0.2')))]", + "id": "[concat(take(variables('_solutionId'),50),'-','hq','-', uniqueString(concat(variables('_solutionId'),'-','HuntingQuery','-',variables('huntingQueryObject21')._huntingQuerycontentId21,'-', '2.0.2')))]", + "version": "2.0.2" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentPackages", + "apiVersion": "2023-04-01-preview", + "location": "[parameters('workspace-location')]", + "properties": { + "version": "3.0.0", + "kind": "Solution", + "contentSchemaVersion": "3.0.0", + "displayName": "Global Secure Access", + "publisherDisplayName": "Microsoft Corporation", + "descriptionHtml": "

Note: Please refer to the following before installing the solution:

\n

ā€¢ Review the solution Release Notes

\n

ā€¢ There may be known issues pertaining to this Solution, please refer to them before installing.

\n

Global Secure Access is a domain solution and does not include any data connectors. The content in this solution requires one of the product solutions below.

\n

Prerequisite:

\n

Install one or more of the listed solutions to unlock the value provided by this solution.

\n
    \n
  1. Microsoft Entra ID
  2. \n
\n

Underlying Microsoft Technologies used:

\n

This solution depends on the following technologies, and some of these dependencies may either be in Preview state or might result in additional ingestion or operational costs:

\n
    \n
  1. Product solutions as described above
  2. \n
\n

Workbooks: 2, Analytic Rules: 19, Hunting Queries: 21

\n

Learn more about Microsoft Sentinel | Learn more about Solutions

\n", + "contentKind": "Solution", + "contentProductId": "[variables('_solutioncontentProductId')]", + "id": "[variables('_solutioncontentProductId')]", + "icon": "", + "contentId": "[variables('_solutionId')]", + "parentId": "[variables('_solutionId')]", + "source": { + "kind": "Solution", + "name": "Global Secure Access", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "name": "Microsoft Corporation", + "email": "GSASentinelSupport@microsoft.com", + "tier": "Partner", + "link": "https://learn.microsoft.com/en-us/entra/global-secure-access/overview-what-is-global-secure-access" + }, + "dependencies": { + "operator": "AND", + "criteria": [ + { + "kind": "Workbook", + "contentId": "[variables('_workbookContentId1')]", + "version": "[variables('workbookVersion1')]" + }, + { + "kind": "Workbook", + "contentId": "[variables('_workbookContentId2')]", + "version": "[variables('workbookVersion2')]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject1')._analyticRulecontentId1]", + "version": "[variables('analyticRuleObject1').analyticRuleVersion1]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject2')._analyticRulecontentId2]", + "version": "[variables('analyticRuleObject2').analyticRuleVersion2]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject3')._analyticRulecontentId3]", + "version": "[variables('analyticRuleObject3').analyticRuleVersion3]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject4')._analyticRulecontentId4]", + "version": "[variables('analyticRuleObject4').analyticRuleVersion4]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject5')._analyticRulecontentId5]", + "version": "[variables('analyticRuleObject5').analyticRuleVersion5]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject6')._analyticRulecontentId6]", + "version": "[variables('analyticRuleObject6').analyticRuleVersion6]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject7')._analyticRulecontentId7]", + "version": "[variables('analyticRuleObject7').analyticRuleVersion7]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject8')._analyticRulecontentId8]", + "version": "[variables('analyticRuleObject8').analyticRuleVersion8]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject9')._analyticRulecontentId9]", + "version": "[variables('analyticRuleObject9').analyticRuleVersion9]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject10')._analyticRulecontentId10]", + "version": "[variables('analyticRuleObject10').analyticRuleVersion10]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject11')._analyticRulecontentId11]", + "version": "[variables('analyticRuleObject11').analyticRuleVersion11]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject12')._analyticRulecontentId12]", + "version": "[variables('analyticRuleObject12').analyticRuleVersion12]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject13')._analyticRulecontentId13]", + "version": "[variables('analyticRuleObject13').analyticRuleVersion13]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject14')._analyticRulecontentId14]", + "version": "[variables('analyticRuleObject14').analyticRuleVersion14]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject15')._analyticRulecontentId15]", + "version": "[variables('analyticRuleObject15').analyticRuleVersion15]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject16')._analyticRulecontentId16]", + "version": "[variables('analyticRuleObject16').analyticRuleVersion16]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject17')._analyticRulecontentId17]", + "version": "[variables('analyticRuleObject17').analyticRuleVersion17]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject18')._analyticRulecontentId18]", + "version": "[variables('analyticRuleObject18').analyticRuleVersion18]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRuleObject19')._analyticRulecontentId19]", + "version": "[variables('analyticRuleObject19').analyticRuleVersion19]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject1')._huntingQuerycontentId1]", + "version": "[variables('huntingQueryObject1').huntingQueryVersion1]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject2')._huntingQuerycontentId2]", + "version": "[variables('huntingQueryObject2').huntingQueryVersion2]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject3')._huntingQuerycontentId3]", + "version": "[variables('huntingQueryObject3').huntingQueryVersion3]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject4')._huntingQuerycontentId4]", + "version": "[variables('huntingQueryObject4').huntingQueryVersion4]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject5')._huntingQuerycontentId5]", + "version": "[variables('huntingQueryObject5').huntingQueryVersion5]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject6')._huntingQuerycontentId6]", + "version": "[variables('huntingQueryObject6').huntingQueryVersion6]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject7')._huntingQuerycontentId7]", + "version": "[variables('huntingQueryObject7').huntingQueryVersion7]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject8')._huntingQuerycontentId8]", + "version": "[variables('huntingQueryObject8').huntingQueryVersion8]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject9')._huntingQuerycontentId9]", + "version": "[variables('huntingQueryObject9').huntingQueryVersion9]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject10')._huntingQuerycontentId10]", + "version": "[variables('huntingQueryObject10').huntingQueryVersion10]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject11')._huntingQuerycontentId11]", + "version": "[variables('huntingQueryObject11').huntingQueryVersion11]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject12')._huntingQuerycontentId12]", + "version": "[variables('huntingQueryObject12').huntingQueryVersion12]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject13')._huntingQuerycontentId13]", + "version": "[variables('huntingQueryObject13').huntingQueryVersion13]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject14')._huntingQuerycontentId14]", + "version": "[variables('huntingQueryObject14').huntingQueryVersion14]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject15')._huntingQuerycontentId15]", + "version": "[variables('huntingQueryObject15').huntingQueryVersion15]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject16')._huntingQuerycontentId16]", + "version": "[variables('huntingQueryObject16').huntingQueryVersion16]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject17')._huntingQuerycontentId17]", + "version": "[variables('huntingQueryObject17').huntingQueryVersion17]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject18')._huntingQuerycontentId18]", + "version": "[variables('huntingQueryObject18').huntingQueryVersion18]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject19')._huntingQuerycontentId19]", + "version": "[variables('huntingQueryObject19').huntingQueryVersion19]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject20')._huntingQuerycontentId20]", + "version": "[variables('huntingQueryObject20').huntingQueryVersion20]" + }, + { + "kind": "HuntingQuery", + "contentId": "[variables('huntingQueryObject21')._huntingQuerycontentId21]", + "version": "[variables('huntingQueryObject21').huntingQueryVersion21]" + } + ] + }, + "firstPublishDate": "2024-04-08", + "providers": [ + "Microsoft" + ], + "categories": { + "domains": [ + "Identity" + ] + } + }, + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/', variables('_solutionId'))]" + } + ], + "outputs": {} +} diff --git a/Solutions/Global Secure Access/Package/testParameters.json b/Solutions/Global Secure Access/Package/testParameters.json new file mode 100644 index 0000000000..8dd674f595 --- /dev/null +++ b/Solutions/Global Secure Access/Package/testParameters.json @@ -0,0 +1,40 @@ +{ + "location": { + "type": "string", + "minLength": 1, + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Not used, but needed to pass arm-ttk test `Location-Should-Not-Be-Hardcoded`. We instead use the `workspace-location` which is derived from the LA workspace" + } + }, + "workspace-location": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "[concat('Region to deploy solution resources -- separate from location selection',parameters('location'))]" + } + }, + "workspace": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Workspace name for Log Analytics where Microsoft Sentinel is setup" + } + }, + "workbook1-name": { + "type": "string", + "defaultValue": "Microsoft Global Secure Access Enriched M365 Logs", + "minLength": 1, + "metadata": { + "description": "Name for the workbook" + } + }, + "workbook2-name": { + "type": "string", + "defaultValue": "Microsoft Global Secure Access Traffic Logs", + "minLength": 1, + "metadata": { + "description": "Name for the workbook" + } + } +} From 85e11558d15084e0613a3a632bdc487fc96ff8c7 Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Mon, 7 Oct 2024 08:57:04 +0300 Subject: [PATCH 20/27] workbooks version --- Workbooks/WorkbooksMetadata.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Workbooks/WorkbooksMetadata.json b/Workbooks/WorkbooksMetadata.json index d00f2276a3..e20201e86f 100644 --- a/Workbooks/WorkbooksMetadata.json +++ b/Workbooks/WorkbooksMetadata.json @@ -8017,8 +8017,8 @@ "GSATrafficLogsWhite.png", "GSATrafficLogsBlack.png" ], - "version": "1.0.1", - "title": "Microsoft Global Secure Access Traffic Logs", + "version": "1.0.2", + "title": "Network Traffic Insights", "templateRelativePath": "GSANetworkTraffic.json", "subtitle": "", "provider": "Microsoft" @@ -8035,8 +8035,8 @@ "GSAEnrichedLogsWhite.png", "GSAEnrichedLogsBlack.png" ], - "version": "1.0.1", - "title": "Microsoft Global Secure Access Enriched M365 Logs", + "version": "1.0.2", + "title": "Enriched Microsoft 365 logs Workbook", "templateRelativePath": "GSAM365EnrichedEvents.json", "provider": "Microsoft" }, From db17f68dc9624db4a9e4ea54a2dd35205af71669 Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Mon, 7 Oct 2024 09:06:54 +0300 Subject: [PATCH 21/27] Update Office 365 - sharepoint_file_transfer_above_threshold.yaml --- .../Office 365 - sharepoint_file_transfer_above_threshold.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml index 56ca53b9f8..27577a50ff 100644 --- a/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml +++ b/Solutions/Global Secure Access/Analytic Rules/Office 365 - sharepoint_file_transfer_above_threshold.yaml @@ -83,5 +83,5 @@ incidentConfiguration: matchingMethod: Selected groupByEntities: - Account -version: 1.0.5 +version: 1.0.6 kind: Scheduled \ No newline at end of file From ed6453dc1e6559cc0d452bfcedfbc5b531c83ee4 Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Mon, 7 Oct 2024 09:31:15 +0300 Subject: [PATCH 22/27] Update WorkbooksMetadata.json --- Workbooks/WorkbooksMetadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Workbooks/WorkbooksMetadata.json b/Workbooks/WorkbooksMetadata.json index e20201e86f..76c5edafa4 100644 --- a/Workbooks/WorkbooksMetadata.json +++ b/Workbooks/WorkbooksMetadata.json @@ -8017,7 +8017,7 @@ "GSATrafficLogsWhite.png", "GSATrafficLogsBlack.png" ], - "version": "1.0.2", + "version": "1.1.0", "title": "Network Traffic Insights", "templateRelativePath": "GSANetworkTraffic.json", "subtitle": "", From 589bb9fb4373cc3a1b2acfffcc5f65d9bdd4f17f Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Mon, 7 Oct 2024 10:06:13 +0300 Subject: [PATCH 23/27] Update WorkbooksMetadata.json --- Workbooks/WorkbooksMetadata.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Workbooks/WorkbooksMetadata.json b/Workbooks/WorkbooksMetadata.json index 76c5edafa4..58625b2b89 100644 --- a/Workbooks/WorkbooksMetadata.json +++ b/Workbooks/WorkbooksMetadata.json @@ -8017,7 +8017,7 @@ "GSATrafficLogsWhite.png", "GSATrafficLogsBlack.png" ], - "version": "1.1.0", + "version": "1.0.3", "title": "Network Traffic Insights", "templateRelativePath": "GSANetworkTraffic.json", "subtitle": "", @@ -8035,7 +8035,7 @@ "GSAEnrichedLogsWhite.png", "GSAEnrichedLogsBlack.png" ], - "version": "1.0.2", + "version": "1.0.3", "title": "Enriched Microsoft 365 logs Workbook", "templateRelativePath": "GSAM365EnrichedEvents.json", "provider": "Microsoft" From 147673952bdf0e336e40c55918fb04c60ad6f4a1 Mon Sep 17 00:00:00 2001 From: v-shukore Date: Mon, 7 Oct 2024 16:39:07 +0530 Subject: [PATCH 24/27] Update WorkbooksMetadata.json --- Workbooks/WorkbooksMetadata.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Workbooks/WorkbooksMetadata.json b/Workbooks/WorkbooksMetadata.json index 58625b2b89..711b8b2513 100644 --- a/Workbooks/WorkbooksMetadata.json +++ b/Workbooks/WorkbooksMetadata.json @@ -8017,7 +8017,7 @@ "GSATrafficLogsWhite.png", "GSATrafficLogsBlack.png" ], - "version": "1.0.3", + "version": "1.0.1", "title": "Network Traffic Insights", "templateRelativePath": "GSANetworkTraffic.json", "subtitle": "", @@ -8035,7 +8035,7 @@ "GSAEnrichedLogsWhite.png", "GSAEnrichedLogsBlack.png" ], - "version": "1.0.3", + "version": "1.0.1", "title": "Enriched Microsoft 365 logs Workbook", "templateRelativePath": "GSAM365EnrichedEvents.json", "provider": "Microsoft" From 5856d7f6cc0929d78c63da00f8d5267468dccef0 Mon Sep 17 00:00:00 2001 From: moti-ba <131643892+moti-ba@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:27:22 +0300 Subject: [PATCH 25/27] keep workbooks in Solution folder only --- Workbooks/GSAM365EnrichedEvents.json | 982 --------------------------- Workbooks/GSANetworkTraffic.json | 787 --------------------- 2 files changed, 1769 deletions(-) delete mode 100644 Workbooks/GSAM365EnrichedEvents.json delete mode 100644 Workbooks/GSANetworkTraffic.json diff --git a/Workbooks/GSAM365EnrichedEvents.json b/Workbooks/GSAM365EnrichedEvents.json deleted file mode 100644 index ea40be7808..0000000000 --- a/Workbooks/GSAM365EnrichedEvents.json +++ /dev/null @@ -1,982 +0,0 @@ -{ - "version": "Notebook/1.0", - "items": [ - { - "type": 1, - "content": { - "json": "## Enriched Microsoft 365 logs Workbook (Preview)\n---\n\nThe enriched Microsoft 365 logs provide information about Microsoft 365 workloads, so you can review network data and security events relevant to Microsoft 365 apps." - }, - "name": "text - 2" - }, - { - "type": 9, - "content": { - "version": "KqlParameterItem/1.0", - "crossComponentResources": [ - "value::all" - ], - "parameters": [ - { - "id": "ff8b2a55-1849-4848-acf8-eab5452e9f10", - "version": "KqlParameterItem/1.0", - "name": "LogAnalyticWorkspace", - "label": "Log Analytic Workspace", - "type": 5, - "description": "The Log Analytic Workspace In Which To Execute The Queries", - "isRequired": true, - "query": "resources\r\n| where type == \"microsoft.operationalinsights/workspaces\"\r\n| project id", - "crossComponentResources": [ - "value::all" - ], - "typeSettings": { - "resourceTypeFilter": { - "microsoft.operationalinsights/workspaces": true - }, - "additionalResourceOptions": [], - "showDefault": false - }, - "timeContext": { - "durationMs": 86400000 - }, - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources", - "value": null - }, - { - "id": "f15f34d8-8e2d-4c39-8dee-be2f979c86a8", - "version": "KqlParameterItem/1.0", - "name": "TimeRange", - "label": "Time Range", - "type": 4, - "isRequired": true, - "typeSettings": { - "selectableValues": [ - { - "durationMs": 300000 - }, - { - "durationMs": 900000 - }, - { - "durationMs": 1800000 - }, - { - "durationMs": 3600000 - }, - { - "durationMs": 14400000 - }, - { - "durationMs": 43200000 - }, - { - "durationMs": 86400000 - }, - { - "durationMs": 172800000 - }, - { - "durationMs": 259200000 - }, - { - "durationMs": 604800000 - }, - { - "durationMs": 1209600000 - }, - { - "durationMs": 2419200000 - }, - { - "durationMs": 2592000000 - } - ], - "allowCustom": true - }, - "timeContext": { - "durationMs": 86400000 - }, - "value": { - "durationMs": 2592000000 - } - }, - { - "id": "8bab511b-53b3-4220-9d1c-372345b06728", - "version": "KqlParameterItem/1.0", - "name": "Users", - "type": 2, - "isRequired": true, - "multiSelect": true, - "quote": "'", - "delimiter": ",", - "query": "EnrichedMicrosoft365AuditLogs\r\n| summarize Count = count() by UserId\r\n| order by Count desc, UserId asc\r\n| project Value = UserId, Label = strcat(UserId, ' - ', Count, ' Logs'), Selected = false", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "typeSettings": { - "limitSelectTo": 20, - "additionalResourceOptions": [ - "value::all" - ], - "selectAllValue": "*", - "showDefault": false - }, - "timeContext": { - "durationMs": 0 - }, - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "value": [ - "value::all" - ] - } - ], - "style": "pills", - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources" - }, - "name": "parameters - 15" - }, - { - "type": 11, - "content": { - "version": "LinkItem/1.0", - "style": "tabs", - "tabStyle": "bigger", - "links": [ - { - "id": "e841bafb-6437-4d29-84ac-ba16c5a6d901", - "cellValue": "selTab", - "linkTarget": "parameter", - "linkLabel": "Overview", - "subTarget": "Overview", - "style": "link" - }, - { - "id": "ac5f2082-50bc-4739-bdf2-20c93b613671", - "cellValue": "selTab", - "linkTarget": "parameter", - "linkLabel": "SharePoint/OneDrive Insights", - "subTarget": "Threat", - "style": "link" - }, - { - "id": "dc2778e7-739b-44ba-9ae4-c81901277f57", - "cellValue": "selTab", - "linkTarget": "parameter", - "linkLabel": "Exchange Online Insights", - "subTarget": "ThreatEXO", - "style": "link" - }, - { - "id": "666111e2-54ff-4fa4-a648-11a5c8c0235b", - "cellValue": "selTab", - "linkTarget": "parameter", - "linkLabel": "Teams Insights", - "subTarget": "ThreatTeams", - "style": "link" - } - ] - }, - "name": "links - 7" - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "items": [ - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| where Workload in (\"Exchange\")\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\r\n| extend \r\n Country = tostring(GeoInfo.country), \r\n State = tostring(GeoInfo.state), \r\n City = tostring(GeoInfo.city), \r\n Latitude = tostring(GeoInfo.latitude), \r\n Longitude = tostring(GeoInfo.longitude)\r\n| project \r\n UserId, \r\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\r\n Latitude, \r\n Longitude, \r\n City, \r\n State, \r\n Country\r\n| summarize Count = count() by City, State, Country\r\n", - "size": 0, - "title": "Access By Location", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "visualization": "map", - "mapSettings": { - "locInfo": "CountryRegion", - "locInfoColumn": "Country", - "latitude": "Latitude", - "longitude": "Longitude", - "sizeSettings": "Count", - "sizeAggregation": "Sum", - "labelSettings": "Country", - "legendMetric": "Count", - "legendAggregation": "Sum", - "itemColorSettings": { - "nodeColorField": "Count", - "colorAggregation": "Sum", - "type": "heatmap", - "heatmapPalette": "turquoise" - } - } - }, - "customWidth": "50", - "name": "query - 0" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| where Workload in (\"Exchange\")\r\n| summarize [\"Count\"] = count() by [\"Source IP\"] = SourceIp, [\"Device Operating System\"] = DeviceOperatingSystem\r\n| order by [\"Count\"] desc\r\n", - "size": 0, - "title": "Access By Location And OS", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ] - }, - "customWidth": "50", - "name": "query - 1" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\r\n| where Operation in (\"Set-AdminAuditLogConfig\", \"Set-OrganizationConfig\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", - "size": 0, - "title": "Audit Of Critical Configuration Changes", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ] - }, - "customWidth": "50", - "name": "query - 2" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\r\n| where Operation in (\"Set-TransportRule\", \"Set-AtpPolicyForO365\", \"Set-MalwareFilterRule\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", - "size": 0, - "title": "Changes In Email Security Policies", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ] - }, - "customWidth": "50", - "name": "query - 3" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\r\n| where Operation in (\"Add-RoleGroupMember\", \"Remove-ManagementRoleAssignment\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", - "size": 0, - "title": "Monitoring Role Group Membership Changes", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ] - }, - "customWidth": "50", - "name": "query - 4" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\r\n| where Operation in (\"New-TenantAllowBlockListSpoofitems\", \"Remove-TenantAllowBlockListSpoofitems\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", - "size": 0, - "title": "Spoofing Settings Management Activities", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ] - }, - "customWidth": "50", - "name": "query - 5" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\r\n| where Operation in (\"New-SafeAttachmentPolicy\", \"Set-SafeAttachmentPolicy\", \"Set-SafeAttachmentRule\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", - "size": 0, - "title": "Safe Attachments Policy Changes", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ] - }, - "customWidth": "50", - "name": "query - 6" - } - ] - }, - "conditionalVisibility": { - "parameterName": "selTab", - "comparison": "isEqualTo", - "value": "ThreatEXO" - }, - "name": "group - 18" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs | where Workload == \"Exchange\" | where Operation in (\"Add-MailboxFolderPermission\", \"Add-RoleGroupMember\", \"New-TransportRule\", \"Remove-ManagementRoleAssignment\", \"Set-TransportRule\") | summarize Count = count(), Users = makeset(UserId) by Operation | order by Count desc", - "size": 0, - "title": "Permission changes and security policy updates", - "timeContext": { - "durationMs": 86400000 - }, - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ] - }, - "conditionalVisibility": { - "parameterName": "seltab", - "comparison": "isEqualTo", - "value": "ThreatEXO" - }, - "name": "query - 13" - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "items": [ - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| where Workload in (\"Teams\")\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\r\n| extend \r\n Country = tostring(GeoInfo.country), \r\n State = tostring(GeoInfo.state), \r\n City = tostring(GeoInfo.city), \r\n Latitude = tostring(GeoInfo.latitude), \r\n Longitude = tostring(GeoInfo.longitude)\r\n| project \r\n UserId, \r\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\r\n Latitude, \r\n Longitude, \r\n City, \r\n State, \r\n Country\r\n| summarize Count = count() by City, State, Country\r\n", - "size": 0, - "title": "Access By Location", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "visualization": "map", - "mapSettings": { - "locInfo": "CountryRegion", - "locInfoColumn": "Country", - "latitude": "Latitude", - "longitude": "Longitude", - "sizeSettings": "Count", - "sizeAggregation": "Sum", - "labelSettings": "Country", - "legendMetric": "Count", - "legendAggregation": "Sum", - "itemColorSettings": { - "nodeColorField": "Count", - "colorAggregation": "Sum", - "type": "heatmap", - "heatmapPalette": "turquoise" - } - } - }, - "customWidth": "50", - "name": "query - 0" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| where Workload in (\"Teams\")\r\n| summarize [\"Count\"] = count() by [\"Source IP\"] = SourceIp, [\"Device Operating System\"] = DeviceOperatingSystem\r\n| order by [\"Count\"] desc\r\n", - "size": 0, - "title": "Access By Location And OS", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ] - }, - "customWidth": "50", - "name": "query - 1" - } - ] - }, - "conditionalVisibility": { - "parameterName": "selTab", - "comparison": "isEqualTo", - "value": "ThreatTeams" - }, - "name": "group - 10" - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "items": [ - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| where Workload in (\"OneDrive\", \"SharePoint\",\"SPO/OneDrive\")\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\r\n| extend \r\n Country = tostring(GeoInfo.country), \r\n State = tostring(GeoInfo.state), \r\n City = tostring(GeoInfo.city), \r\n Latitude = tostring(GeoInfo.latitude), \r\n Longitude = tostring(GeoInfo.longitude)\r\n| project \r\n UserId, \r\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\r\n Latitude, \r\n Longitude, \r\n City, \r\n State, \r\n Country\r\n| summarize Count = count() by City, State, Country\r\n", - "size": 0, - "title": "Access By Location", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "visualization": "map", - "mapSettings": { - "locInfo": "CountryRegion", - "locInfoColumn": "Country", - "latitude": "Latitude", - "longitude": "Longitude", - "sizeSettings": "Count", - "sizeAggregation": "Sum", - "labelSettings": "Country", - "legendMetric": "Country", - "numberOfMetrics": 19, - "legendAggregation": "Count", - "itemColorSettings": { - "nodeColorField": "Count", - "colorAggregation": "Sum", - "type": "heatmap", - "heatmapPalette": "turquoise" - } - } - }, - "customWidth": "50", - "conditionalVisibility": { - "parameterName": "selTab", - "comparison": "isEqualTo", - "value": "Threat" - }, - "name": "query - 19" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| where Workload in (\"OneDrive\", \"SharePoint\", \"SPO/OneDrive\")\r\n| summarize [\"Event Count\"] = count() by [\"Source IP\"] = SourceIp, [\"Device Operating System\"] = DeviceOperatingSystem\r\n| order by [\"Event Count\"] desc\r\n", - "size": 2, - "title": "Access By Location And OS", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "gridSettings": { - "filter": true - } - }, - "customWidth": "50", - "name": "query - 20" - } - ] - }, - "conditionalVisibility": { - "parameterName": "selTab", - "comparison": "isEqualTo", - "value": "Threat" - }, - "name": "group - 20", - "styleSettings": { - "showBorder": true - } - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\r\n| where Workload == \"Teams\"\r\n| where Operation in (\"MemberAdded\", \"MemberRemoved\", \"TeamDeleted\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", - "size": 0, - "title": "Changes In Team Memberships", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ] - }, - "conditionalVisibility": { - "parameterName": "selTab", - "comparison": "isEqualTo", - "value": "ThreatTeams" - }, - "customWidth": "50", - "name": "query - 11" - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "items": [ - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "let RiskyExtensions = dynamic([\"exe\", \"msi\", \"bat\", \"cmd\", \"com\", \"scr\", \"pif\", \"ps1\", \"vbs\", \"js\", \"jse\", \"wsf\", \"docm\", \"xlsm\", \"pptm\", \"dll\", \"ocx\", \"cpl\", \"app\", \"vb\", \"reg\", \"inf\", \"hta\"]);\r\n\r\nEnrichedMicrosoft365AuditLogs\r\n| where Workload in (\"SPO/OneDrive\")\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| extend [\"File Extension\"] = tostring(parse_json(AdditionalProperties).SourceFileExtension)\r\n| where [\"File Extension\"] in (RiskyExtensions)\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, [\"File Extension\"], Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], [\"File Extension\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], [\"File Extension\"], Operation, Count\r\n", - "size": 0, - "title": "Risky File Operations", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "gridSettings": { - "formatters": [ - { - "columnMatch": "OperationCount", - "formatter": 4, - "formatOptions": { - "palette": "blue" - } - } - ] - } - }, - "customWidth": "50", - "conditionalVisibility": { - "parameterName": "selTab", - "comparison": "isEqualTo", - "value": "Threat" - }, - "name": "query - 1" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\r\n| where Workload in (\"OneDrive\", \"SharePoint\", \"SPO/OneDrive\")\r\n| where Operation in (\"FileRecycled\", \"FileDownloaded\", \"FileUploaded\", \"FileCreated\", \"File Modified\")\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by Timestamp, [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", - "size": 0, - "title": "Bulk File Events", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "gridSettings": { - "formatters": [ - { - "columnMatch": "OperationCount", - "formatter": 4, - "formatOptions": { - "palette": "blue" - } - } - ] - } - }, - "customWidth": "50", - "conditionalVisibility": { - "parameterName": "selTab", - "comparison": "isEqualTo", - "value": "Threat" - }, - "name": "query - 8" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\r\n| where Workload in (\"SPO/OneDrive\")\r\n| where Operation == \"FileDeletedFirstStageRecycleBin\"\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by bin(Timestamp, 1h), [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n| where Count > 1\r\n", - "size": 0, - "title": " Bulk File Deletion Operations", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "gridSettings": { - "formatters": [ - { - "columnMatch": "FileDeletions", - "formatter": 8, - "formatOptions": { - "palette": "blue" - } - } - ] - } - }, - "customWidth": "50", - "conditionalVisibility": { - "parameterName": "selTab", - "comparison": "isEqualTo", - "value": "Threat" - }, - "name": "query - 3" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "let baselinePeriod = 30d;\r\nlet detectionWindow = 1h;\r\nlet downloadThreshold = 5; // Threshold of downloads indicating potential exfiltration\r\n\r\nEnrichedMicrosoft365AuditLogs\r\n| where TimeGenerated >= ago(baselinePeriod)\r\n| where Workload in (\"OneDrive\", \"SharePoint\", \"SPO/OneDrive\")\r\n| where Operation == \"FileDownloaded\"\r\n| project Timestamp = TimeGenerated, [\"User ID\"] = UserId, [\"Client IP\"] = ClientIp, Operation\r\n| summarize Count = count() by bin(Timestamp, detectionWindow), [\"User ID\"], [\"Client IP\"], Operation\r\n| project Timestamp, [\"User ID\"], [\"Client IP\"], Operation, Count\r\n| order by Count desc\r\n", - "size": 0, - "title": "Bulk File Download", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "gridSettings": { - "formatters": [ - { - "columnMatch": "DownloadCount", - "formatter": 8, - "formatOptions": { - "palette": "blue" - } - } - ] - } - }, - "customWidth": "50", - "conditionalVisibility": { - "parameterName": "selTab", - "comparison": "isEqualTo", - "value": "Threat" - }, - "name": "query - 4" - } - ] - }, - "name": "group - 9" - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "items": [ - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Assuming a function exists to pull geo info\r\n| extend \r\n Country = tostring(GeoInfo.country), \r\n State = tostring(GeoInfo.state), \r\n City = tostring(GeoInfo.city), \r\n Latitude = tostring(GeoInfo.latitude), \r\n Longitude = tostring(GeoInfo.longitude)\r\n| project \r\n UserId, \r\n Location = strcat(City, ', ', State, ', ', Country), // Constructing a full location string\r\n Latitude, \r\n Longitude, \r\n City, \r\n State, \r\n Country\r\n | where tostring(Country) != \"\"\r\n| summarize Count = count() by City, State, Country\r\n\r\n\r\n", - "size": 0, - "title": "Access By Location", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "visualization": "map", - "mapSettings": { - "locInfo": "CountryRegion", - "locInfoColumn": "Country", - "latitude": "Latitude", - "longitude": "Longitude", - "sizeSettings": "Count", - "sizeAggregation": "Sum", - "minSize": 20, - "labelSettings": "Country", - "legendMetric": "Country", - "numberOfMetrics": 8, - "legendAggregation": "Count", - "itemColorSettings": { - "nodeColorField": "Count", - "colorAggregation": "Sum", - "type": "heatmap", - "heatmapPalette": "turquoise" - } - } - }, - "customWidth": "50", - "conditionalVisibility": { - "parameterName": "selTab", - "comparison": "isEqualTo", - "value": "Overview" - }, - "name": "query - 5" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| summarize [\"Event Count\"] = count() by [\"Time Generated\"] = TimeGenerated, [\"User\"] = UserId, [\"Source IP\"] = SourceIp, [\"Device Operating System\"] = DeviceOperatingSystem, [\"Workload\"] = Workload\r\n| order by [\"Time Generated\"] desc\r\n", - "size": 0, - "title": "Access By Location And OS", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "gridSettings": { - "rowLimit": 1000, - "filter": true - } - }, - "customWidth": "50", - "conditionalVisibility": { - "parameterName": "selTab", - "comparison": "isEqualTo", - "value": "Overview" - }, - "name": "query - 1" - } - ] - }, - "name": "group - 19" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "let data = EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| project \r\n Operation, \r\n UserId, \r\n Workload, \r\n SourceIp, \r\n DeviceId, \r\n TimeGenerated, \r\n Details = pack_all(),\r\n OS = tostring(DeviceOperatingSystem)\r\n| extend Workload = tostring(Workload)\r\n| extend WorkloadStatus = case(\r\n Workload == \"Exchange\", \"Exchange\",\r\n Workload == \"Teams\", \"Teams\",\r\n Workload == \"SharePoint\", \"SharePoint\",\r\n \"Other\"\r\n);\r\n\r\nlet appData = data\r\n| summarize \r\n TotalCount = count(), \r\n ExchangeCount = countif(Workload == \"Exchange\"), \r\n TeamsCount = countif(Workload == \"Teams\"), \r\n SharePointCount = countif(Workload == \"SharePoint\"), \r\n OtherCount = countif(Workload == \"Other\") \r\n by UserId\r\n| where UserId != ''\r\n| join kind=inner (\r\n data\r\n | make-series Trend = count() default = 0 on TimeGenerated in range({TimeRange:start}, {TimeRange:end}, {TimeRange:grain}) by UserId\r\n | project-away TimeGenerated\r\n ) on UserId\r\n| order by TotalCount desc, UserId asc\r\n| project UserId, TotalCount, ExchangeCount, TeamsCount, SharePointCount, OtherCount, Trend\r\n| serialize Id = row_number();\r\n\r\ndata\r\n| summarize \r\n TotalCount = count(), \r\n ExchangeCount = countif(Workload == \"Exchange\"), \r\n TeamsCount = countif(Workload == \"Teams\"), \r\n SharePointCount = countif(Workload == \"SharePoint\"), \r\n OtherCount = countif(Workload == \"Other\") \r\n by UserId, SourceIp = tostring(SourceIp)\r\n| join kind=inner (\r\n data\r\n | make-series Trend = count() default = 0 on TimeGenerated in range({TimeRange:start}, {TimeRange:end}, {TimeRange:grain}) by UserId, SourceIp\r\n | project-away TimeGenerated\r\n ) on UserId, SourceIp\r\n| order by TotalCount desc, UserId asc\r\n| project UserId, SourceIp, TotalCount, ExchangeCount, TeamsCount, SharePointCount, OtherCount, Trend\r\n| serialize Id = row_number(1000000)\r\n| join kind=inner (appData) on UserId\r\n| project Id, Name = SourceIp, Type = 'Client IP', ['Events Count'] = TotalCount, Trend, ['Exchange Events'] = ExchangeCount, ['Teams Events'] = TeamsCount, ['SharePoint Events'] = SharePointCount, ['Other Count'] = OtherCount, ParentId = Id1\r\n| union (\r\n appData \r\n | project Id, Name = UserId, Type = 'Operating System', ['Events Count'] = TotalCount, Trend, ['Exchange Events'] = ExchangeCount, ['Teams Events'] = TeamsCount, ['SharePoint Events'] = SharePointCount, ['Other Count'] = OtherCount, ParentId = -1\r\n )\r\n| order by ['Events Count'] desc, Name asc\r\n", - "size": 2, - "title": "Activity Log", - "timeContextFromParameter": "TimeRange", - "showRefreshButton": true, - "showExportToExcel": true, - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "gridSettings": { - "formatters": [ - { - "columnMatch": "Id", - "formatter": 5 - }, - { - "columnMatch": "Type", - "formatter": 5 - }, - { - "columnMatch": "Trend", - "formatter": 9, - "formatOptions": { - "palette": "blue" - } - }, - { - "columnMatch": "Other Count", - "formatter": 5 - }, - { - "columnMatch": "ParentId", - "formatter": 5 - }, - { - "columnMatch": "Operation Count", - "formatter": 8, - "formatOptions": { - "palette": "blue" - } - }, - { - "columnMatch": "Exchange Count", - "formatter": 8, - "formatOptions": { - "min": 0, - "palette": "blue" - } - }, - { - "columnMatch": "Teams Count", - "formatter": 8, - "formatOptions": { - "min": 0, - "palette": "purple" - } - }, - { - "columnMatch": "SharePoint Count", - "formatter": 8, - "formatOptions": { - "min": 0, - "palette": "turquoise" - } - }, - { - "columnMatch": "Details", - "formatter": 5, - "formatOptions": { - "linkTarget": "GenericDetails", - "linkIsContextBlade": true - } - } - ], - "rowLimit": 1000, - "filter": true, - "hierarchySettings": { - "idColumn": "Id", - "parentColumn": "ParentId", - "treeType": 0, - "expanderColumn": "Name" - } - } - }, - "conditionalVisibility": { - "parameterName": "selTab", - "comparison": "isEqualTo", - "value": "Overview" - }, - "name": "query - 6" - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "items": [ - { - "type": 1, - "content": { - "json": "
\r\nšŸ’” _Click on a segment of the pie chart to explore more details_" - }, - "conditionalVisibility": { - "parameterName": "selTab", - "comparison": "isEqualTo", - "value": "Overview" - }, - "name": "text - 2" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\r\n| extend \r\n OS = coalesce(DeviceOperatingSystem, \"Unknown OS\"),\r\n OSVersion = coalesce(tostring(DeviceOperatingSystemVersion), \"Unknown Version\")\r\n| summarize DeviceCount = count() by OS, OSVersion\r\n| order by DeviceCount desc\r\n", - "size": 3, - "title": "Devices Accessing M365", - "timeContextFromParameter": "TimeRange", - "exportParameterName": "Parampie", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "visualization": "piechart" - }, - "customWidth": "50", - "conditionalVisibility": { - "parameterName": "selTab", - "comparison": "isEqualTo", - "value": "Overview" - }, - "name": "query - 6" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| extend \r\n [\"Operating System\"] = coalesce(DeviceOperatingSystem, \"Unknown OS\"),\r\n [\"OS Version\"] = coalesce(tostring(DeviceOperatingSystemVersion), \"Unknown Version\"),\r\n [\"Device ID\"] = coalesce(tostring(DeviceId), \"Unknown DeviceId\")\r\n| where [\"Operating System\"] == dynamic({Parampie}).label\r\n| summarize [\"Device Count\"] = count() by [\"Operating System\"], [\"OS Version\"], [\"Device ID\"]\r\n| order by [\"Device Count\"] desc\r\n", - "size": 2, - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "gridSettings": { - "formatters": [ - { - "columnMatch": "$gen_group", - "formatter": 1 - } - ], - "hierarchySettings": { - "treeType": 1, - "groupBy": [ - "OperatingSystem", - "OSVersion" - ], - "expandTopLevel": false - } - } - }, - "customWidth": "50", - "conditionalVisibilities": [ - { - "parameterName": "selTab", - "comparison": "isEqualTo", - "value": "Overview" - }, - { - "parameterName": "Parampie", - "comparison": "isNotEqualTo" - } - ], - "name": "query - 1" - } - ] - }, - "name": "group - 19" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "let BusinessHoursStart = 8; // 8 AM\r\nlet BusinessHoursEnd = 18; // 6 PM\r\n\r\nEnrichedMicrosoft365AuditLogs \r\n| where UserId in ({Users}) or '*' in ({Users})\r\n| extend HourOfDay = hourofday(TimeGenerated)\r\n| where HourOfDay < BusinessHoursStart or HourOfDay > BusinessHoursEnd\r\n| summarize [\"Off-Hour Activities\"] = count() by [\"User ID\"] = UserId, [\"Date\"] = bin(TimeGenerated, 1d), [\"Operation\"]\r\n| order by [\"Off-Hour Activities\"] desc\r\n", - "size": 0, - "title": "Activity Outside Standard Working Hours (8:00 - 18:00)", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ] - }, - "conditionalVisibility": { - "parameterName": "selTab", - "comparison": "isEqualTo", - "value": "Overview" - }, - "name": "query - 14" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "EnrichedMicrosoft365AuditLogs\n| summarize Count = count() by bin(TimeGenerated, 1h)\n| order by TimeGenerated asc\n", - "size": 0, - "title": "Microsoft 365 Transactions", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "visualization": "unstackedbar" - }, - "conditionalVisibility": { - "parameterName": "selTab", - "comparison": "isEqualTo", - "value": "Overview" - }, - "name": "query - 2" - } - ], - "fallbackResourceIds": [ - "Global Secure Access" - ], - "fromTemplateId": "GSA Enriched Microsoft 365 logs", - "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json" -} \ No newline at end of file diff --git a/Workbooks/GSANetworkTraffic.json b/Workbooks/GSANetworkTraffic.json deleted file mode 100644 index e4e50d998a..0000000000 --- a/Workbooks/GSANetworkTraffic.json +++ /dev/null @@ -1,787 +0,0 @@ -{ - "version": "Notebook/1.0", - "items": [ - { - "type": 1, - "content": { - "json": "## Network Traffic Insights Workbook (Preview)\n---\nInformation in the dashboard is based on log data\n\n\n" - }, - "name": "text - 2" - }, - { - "type": 9, - "content": { - "version": "KqlParameterItem/1.0", - "crossComponentResources": [ - "value::all" - ], - "parameters": [ - { - "id": "ff8b2a55-1849-4848-acf8-eab5452e9f10", - "version": "KqlParameterItem/1.0", - "name": "LogAnalyticWorkspace", - "label": "Log Analytic Workspace", - "type": 5, - "description": "The Log Analytic Workspace In Which To Execute The Queries", - "isRequired": true, - "query": "resources\r\n| where type == \"microsoft.operationalinsights/workspaces\"\r\n| project id", - "crossComponentResources": [ - "value::all" - ], - "typeSettings": { - "resourceTypeFilter": { - "microsoft.operationalinsights/workspaces": true - }, - "additionalResourceOptions": [], - "showDefault": false - }, - "timeContext": { - "durationMs": 2592000000 - }, - "queryType": 1, - "resourceType": "microsoft.resourcegraph/resources", - "value": null - }, - { - "id": "f15f34d8-8e2d-4c39-8dee-be2f979c86a8", - "version": "KqlParameterItem/1.0", - "name": "TimeRange", - "label": "Time Range", - "type": 4, - "isRequired": true, - "typeSettings": { - "selectableValues": [ - { - "durationMs": 300000 - }, - { - "durationMs": 900000 - }, - { - "durationMs": 1800000 - }, - { - "durationMs": 3600000 - }, - { - "durationMs": 14400000 - }, - { - "durationMs": 43200000 - }, - { - "durationMs": 86400000 - }, - { - "durationMs": 172800000 - }, - { - "durationMs": 259200000 - }, - { - "durationMs": 604800000 - }, - { - "durationMs": 1209600000 - }, - { - "durationMs": 2419200000 - }, - { - "durationMs": 2592000000 - } - ], - "allowCustom": true - }, - "timeContext": { - "durationMs": 86400000 - }, - "value": { - "durationMs": 604800000 - } - }, - { - "id": "8bab511b-53b3-4220-9d1c-372345b06728", - "version": "KqlParameterItem/1.0", - "name": "Users", - "type": 2, - "isRequired": true, - "multiSelect": true, - "quote": "'", - "delimiter": ",", - "query": "NetworkAccessTraffic\r\n| summarize Count = count() by UserPrincipalName\r\n| order by Count desc, UserPrincipalName asc\r\n| project Value = UserPrincipalName, Label = strcat(UserPrincipalName, ' - ', Count, ' Logs'), Selected = false", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "typeSettings": { - "limitSelectTo": 20, - "additionalResourceOptions": [ - "value::all" - ], - "selectAllValue": "*", - "showDefault": false - }, - "timeContext": { - "durationMs": 0 - }, - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "value": [ - "value::all" - ] - }, - { - "id": "527af4d2-3089-4aa4-9fbb-48ec697db20d", - "version": "KqlParameterItem/1.0", - "name": "WebCategories", - "label": "Web Categories", - "type": 2, - "multiSelect": true, - "quote": "'", - "delimiter": ",", - "query": "NetworkAccessTraffic\r\n| extend firstCategory = tostring(split(DestinationWebCategories, ',')[0]) // Split and get the first category\r\n| summarize Count = count() by firstCategory\r\n| order by Count desc, firstCategory asc\r\n| project Value = firstCategory, Label = strcat(firstCategory, ' - ', Count, ' Logs')", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "typeSettings": { - "additionalResourceOptions": [ - "value::all" - ], - "selectAllValue": "*", - "showDefault": false - }, - "timeContext": { - "durationMs": 604800000 - }, - "timeContextFromParameter": "TimeRange", - "defaultValue": "value::all", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "value": [ - "value::all" - ] - } - ], - "style": "pills", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" - }, - "name": "parameters - 15" - }, - { - "type": 11, - "content": { - "version": "LinkItem/1.0", - "style": "tabs", - "links": [ - { - "id": "2b2cd1be-9d25-412c-8444-f005c4789b55", - "cellValue": "tabSel", - "linkTarget": "parameter", - "linkLabel": "Overview", - "subTarget": "Overview", - "style": "link" - }, - { - "id": "cc3e67f2-f20f-4430-8dee-d0773b90d9ce", - "cellValue": "tabSel", - "linkTarget": "parameter", - "linkLabel": "All Traffic", - "subTarget": "AllTraffic", - "style": "link" - }, - { - "id": "5ae54b5a-ac7b-4b7a-a1e1-1e574625caa3", - "cellValue": "tabSel", - "linkTarget": "parameter", - "linkLabel": "URL Lookup", - "subTarget": "URLLookup", - "style": "link" - }, - { - "id": "68c566f8-957e-4a3f-8b66-730fc24135fb", - "cellValue": "tabSel", - "linkTarget": "parameter", - "linkLabel": "Security Insights", - "subTarget": "Security", - "style": "link" - } - ] - }, - "name": "links - 7" - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "items": [ - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "let NetworkAccessTrafficData = NetworkAccessTraffic\r\n| where SourceIp != \"\" and isnotempty(SourceIp)\r\n| summarize arg_max(TimeGenerated, *) by SourceIp, TenantId;\r\n\r\nlet SigninLogsData = SigninLogs\r\n| where IPAddress != \"\" and isnotempty(IPAddress)\r\n| summarize arg_max(TimeGenerated, *) by IPAddress, TenantId, UserId, CorrelationId;\r\n\r\nSigninLogsData\r\n| join kind=leftanti (\r\n NetworkAccessTrafficData\r\n | where SourceIp != \"\" and isnotempty(SourceIp)\r\n) on $left.IPAddress == $right.SourceIp and $left.TenantId == $right.TenantId\r\n| project TimeGenerated, IPAddress, UserId, UserPrincipalName, AppDisplayName, DeviceDetail.deviceId\r\n", - "size": 0, - "title": "Sign-ins Outside Global Secure Access", - "timeContextFromParameter": "TimeRange", - "showExportToExcel": true, - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "gridSettings": { - "rowLimit": 1000, - "filter": true - } - }, - "name": "query - 0" - } - ] - }, - "conditionalVisibility": { - "parameterName": "tabSel", - "comparison": "isEqualTo", - "value": "Security" - }, - "name": "group - 10" - }, - { - "type": 1, - "content": { - "json": "šŸ’” Type the URL for detailed analysis" - }, - "conditionalVisibility": { - "parameterName": "tabSel", - "comparison": "isEqualTo", - "value": "URLLookup" - }, - "name": "text - 10" - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "items": [ - { - "type": 9, - "content": { - "version": "KqlParameterItem/1.0", - "parameters": [ - { - "id": "ec8c38c8-3064-4921-8dcd-a69d3895599b", - "version": "KqlParameterItem/1.0", - "name": "URL", - "type": 1, - "typeSettings": { - "isSearchBox": true - }, - "timeContext": { - "durationMs": 86400000 - } - } - ], - "style": "above", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces" - }, - "conditionalVisibility": { - "parameterName": "tabSel", - "comparison": "isEqualTo", - "value": "URLLookup" - }, - "name": "parameters - 9" - }, - { - "type": 1, - "content": { - "json": "# Web Categories" - }, - "name": "text - 11" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "let CategoryData = NetworkAccessTraffic \r\n| where DestinationFqdn contains \"{URL}\" // Filters logs based on the provided URL parameter\r\n| extend CategoriesList = split(DestinationWebCategories, \",\") // Split categories into a list\r\n| mv-expand Category = CategoriesList // Expand the list into individual rows\r\n| extend Category = trim_start(\" \", trim_end(\" \", tostring(Category))) // Trim leading and trailing spaces\r\n| extend Category = iff(TrafficType == \"microsoft365\", \"Microsoft M365\", Category) // Set category to \"Microsoft M365\" if TrafficType is \"microsoft365\"\r\n| summarize UniqueCategories = make_set(Category); // Create a set of unique categories\r\n\r\nCategoryData\r\n| extend \r\n PrimaryCategory = iff(array_length(UniqueCategories) > 0, tostring(UniqueCategories[0]), \"None\")\r\n| project \r\n col1 = PrimaryCategory,\r\n snapshot = \"Primary Category\"\r\n\r\n| union (\r\n CategoryData\r\n | extend \r\n SecondaryCategory = iff(array_length(UniqueCategories) > 1, tostring(UniqueCategories[1]), \"None\")\r\n | project \r\n col1 = SecondaryCategory,\r\n snapshot = \"Secondary Category\"\r\n)\r\n\r\n| order by snapshot asc\r\n", - "size": 4, - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "visualization": "tiles", - "tileSettings": { - "titleContent": { - "columnMatch": "snapshot" - }, - "leftContent": { - "columnMatch": "col1", - "numberFormat": { - "unit": 17, - "options": { - "style": "decimal", - "maximumFractionDigits": 2 - } - } - }, - "showBorder": true, - "size": "auto" - } - }, - "name": "query - 17" - }, - { - "type": 1, - "content": { - "json": "# Traffic Access Details" - }, - "name": "text - 15" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "let NetworkData = NetworkAccessTraffic\r\n| where DestinationFqdn contains \"{URL}\"; // Filters logs based on the provided URL parameter\r\n\r\nNetworkData\r\n| summarize \r\n UniqueUsers = dcount(UserPrincipalName),\r\n UniqueDevices = dcount(DeviceId), \r\n TotalAllow = countif(Action == \"Allow\"),\r\n TotalBlock = countif(Action == \"Block\")\r\n| project \r\n col1 = UniqueUsers, \r\n snapshot = \"Unique Users\"\r\n| union (NetworkData\r\n | summarize UniqueDevices = dcount(DeviceId)\r\n | project col1 = UniqueDevices, snapshot = \"Unique Devices\")\r\n| union (NetworkData\r\n | summarize TotalAllow = countif(Action == \"Allow\")\r\n | project col1 = TotalAllow, snapshot = \"Total Allow\")\r\n| union (NetworkData\r\n | summarize TotalBlock = countif(Action == \"Block\")\r\n | project col1 = TotalBlock, snapshot = \"Total Block\")\r\n| order by snapshot asc", - "size": 4, - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "visualization": "tiles", - "tileSettings": { - "titleContent": { - "columnMatch": "snapshot", - "formatter": 1 - }, - "leftContent": { - "columnMatch": "col1", - "formatter": 12, - "formatOptions": { - "palette": "auto" - }, - "numberFormat": { - "unit": 17, - "options": { - "style": "decimal", - "maximumFractionDigits": 2, - "maximumSignificantDigits": 3 - } - } - }, - "showBorder": true - } - }, - "name": "query - 14" - }, - { - "type": 1, - "content": { - "json": "# Bandwidth Usage" - }, - "name": "text - 16" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "let NetworkData = NetworkAccessTraffic\r\n| where DestinationFqdn contains \"{URL}\"; // Filters logs based on the provided URL parameter\r\n\r\nNetworkData\r\n| summarize \r\n TotalSent = sum(SentBytes),\r\n TotalReceived = sum(ReceivedBytes),\r\n TotalBandwidth = sum(SentBytes + ReceivedBytes)\r\n| extend \r\n TotalSentMB = round(TotalSent / 1024.0 / 1024.0, 2),\r\n TotalReceivedMB = round(TotalReceived / 1024.0 / 1024.0, 2),\r\n TotalBandwidthMB = round(TotalBandwidth / 1024.0 / 1024.0, 2)\r\n| project \r\n TotalSentMB,\r\n TotalReceivedMB,\r\n TotalBandwidthMB\r\n| extend dummy = 1\r\n| project-away dummy\r\n| mv-expand \r\n Column = pack_array(\"TotalSentMB\", \"TotalReceivedMB\", \"TotalBandwidthMB\"),\r\n Value = pack_array(TotalSentMB, TotalReceivedMB, TotalBandwidthMB)\r\n| extend \r\n snapshot = case(\r\n Column == \"TotalSentMB\", \"Total Sent (MB)\",\r\n Column == \"TotalReceivedMB\", \"Total Received (MB)\",\r\n Column == \"TotalBandwidthMB\", \"Total Bandwidth (MB)\",\r\n \"Unknown\"\r\n ),\r\n col1 = iff(Value < 0.01, 0.00, Value)\r\n| project-away Column, Value\r\n| order by snapshot asc", - "size": 4, - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "visualization": "tiles", - "tileSettings": { - "titleContent": { - "columnMatch": "snapshot", - "formatter": 1 - }, - "leftContent": { - "columnMatch": "col1", - "formatter": 12, - "formatOptions": { - "palette": "auto" - }, - "numberFormat": { - "unit": 17, - "options": { - "style": "decimal", - "maximumFractionDigits": 2, - "maximumSignificantDigits": 3 - } - } - }, - "showBorder": true - } - }, - "name": "query - 17" - }, - { - "type": 1, - "content": { - "json": "# Traffic Logs (filtered)" - }, - "name": "text - 12" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| where DestinationFqdn contains \"{URL}\"\r\n| project \r\n Timestamp = TimeGenerated,\r\n User = UserPrincipalName,\r\n [\"Source IP\"] = SourceIp,\r\n [\"Destination IP\"] = DestinationIp,\r\n [\"Destination Port\"] = DestinationPort,\r\n [\"Destination FQDN\"] = DestinationFqdn,\r\n Action = case(\r\n tolower(Action) == \"allow\", \"šŸŸ¢ Allow\", \r\n tolower(Action) == \"block\", \"šŸ”“ Block\", \r\n tolower(Action) // This returns the action in lowercase if it doesn't match \"allow\" or \"block\"\r\n ),\r\n [\"Policy Name\"] = PolicyName,\r\n [\"Policy Rule ID\"] = PolicyRuleId,\r\n [\"Received Bytes\"] = ReceivedBytes,\r\n [\"Sent Bytes\"] = SentBytes,\r\n [\"Device OS\"] = DeviceOperatingSystem \r\n| order by Timestamp desc\r\n", - "size": 0, - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ] - }, - "name": "query - 13" - } - ] - }, - "conditionalVisibility": { - "parameterName": "tabSel", - "comparison": "isEqualTo", - "value": "URLLookup" - }, - "name": "group - URL Lookup" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| project \r\n Timestamp = TimeGenerated,\r\n User = UserPrincipalName,\r\n [\"Source IP\"] = SourceIp,\r\n [\"Destination IP\"] = DestinationIp,\r\n [\"Destination Port\"] = DestinationPort,\r\n [\"Destination FQDN\"] = DestinationFqdn,\r\n Action = case(\r\n tolower(Action) == \"allow\", \"šŸŸ¢ Allow\", \r\n tolower(Action) == \"block\", \"šŸ”“ Block\", \r\n tolower(Action) // This returns the action in lowercase if it doesn't match \"allow\" or \"block\"\r\n ),\r\n [\"Policy Name\"] = PolicyName,\r\n [\"Web Category\"] = DestinationWebCategories,\r\n [\"Transport Protocol\"] = TransportProtocol,\r\n [\"Traffic Type\"] = TrafficType,\r\n [\"Received Bytes\"] = ReceivedBytes,\r\n [\"Sent Bytes\"] = SentBytes,\r\n [\"Device OS\"] = DeviceOperatingSystem,\r\n [\"Policy Rule ID\"] = PolicyRuleId\r\n| order by Timestamp desc\r\n", - "size": 3, - "title": "Traffic Logs", - "timeContextFromParameter": "TimeRange", - "showRefreshButton": true, - "showExportToExcel": true, - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "gridSettings": { - "rowLimit": 1000, - "filter": true - } - }, - "conditionalVisibility": { - "parameterName": "tabSel", - "comparison": "isEqualTo", - "value": "AllTraffic" - }, - "name": "query - 6" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "// Unique Users\nNetworkAccessTraffic\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Extend each row with geolocation info\n| project SourceIp, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\n| summarize UniqueUsers=dcount(Country)\n| extend snapshot = \"Total Locations\"\n| project col1 = UniqueUsers, snapshot\n\n// Union with Unique Devices\n| union (\n NetworkAccessTraffic\n | where UserPrincipalName in ({Users}) or '*' in ({Users})\n | extend BytesInGB = todouble(SentBytes + ReceivedBytes) / (1024 * 1024 * 1024) // Convert bytes to gigabytes\n | summarize TotalBytesGB = sum(BytesInGB)\n | extend snapshot = \"Total Bytes (GB)\"\n | project col1 = tolong(TotalBytesGB), snapshot\n)\n\n// Union with Total Internet Access\n| union (\n NetworkAccessTraffic\n | where UserPrincipalName in ({Users}) or '*' in ({Users})\n | summarize TotalTransactions = count()\n | extend snapshot = \"Total Transactions\"\n | project col1 = TotalTransactions, snapshot\n)\n\n// Union with Total Private Access\n// Order by Snapshot for consistent tile ordering on dashboard\n| order by snapshot", - "size": 4, - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "visualization": "tiles", - "tileSettings": { - "titleContent": { - "columnMatch": "snapshot", - "formatter": 1 - }, - "leftContent": { - "columnMatch": "col1", - "formatter": 12, - "formatOptions": { - "palette": "auto" - } - }, - "showBorder": true, - "size": "auto" - }, - "mapSettings": { - "locInfo": "LatLong", - "sizeSettings": "ExistingClients", - "sizeAggregation": "Sum", - "legendMetric": "ExistingClients", - "legendAggregation": "Sum", - "itemColorSettings": { - "type": "heatmap", - "colorAggregation": "Sum", - "nodeColorField": "ExistingClients", - "heatmapPalette": "greenRed" - } - }, - "textSettings": { - "style": "bignumber" - } - }, - "conditionalVisibility": { - "parameterName": "tabSel", - "comparison": "isEqualTo", - "value": "Overview" - }, - "name": "query - 2" - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "items": [ - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| extend BytesIn = todouble(SentBytes + ReceivedBytes) / (1024 * 1024) // Convert bytes to Mbytes\r\n| summarize TotalBytesMB = sum(BytesIn) by bin(TimeGenerated, 1h), TrafficType\r\n| order by bin(TimeGenerated, 1h) asc, TrafficType asc\r\n| project TimeGenerated, TrafficType, TotalBytesMB\r\n", - "size": 2, - "title": "Usage Over Time (MB)", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "visualization": "barchart" - }, - "conditionalVisibility": { - "parameterName": "tabSel", - "comparison": "isEqualTo", - "value": "Overview" - }, - "name": "query - 0" - } - ] - }, - "name": "group - 5" - }, - { - "type": 12, - "content": { - "version": "NotebookGroup/1.0", - "groupType": "editable", - "items": [ - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| extend GeoInfo = geo_info_from_ip_address(SourceIp) // Extend each row with geolocation info\r\n| project TimeGenerated, SourceIp, Country = tostring(GeoInfo.country), State = tostring(GeoInfo.state), City = tostring(GeoInfo.city), Latitude = tostring(GeoInfo.latitude), Longitude = tostring(GeoInfo.longitude)\r\n| where Country != \"\"\r\n| summarize Count = count() by City, State, Country\r\n\r\n", - "size": 3, - "title": "Locations", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "visualization": "map", - "mapSettings": { - "locInfo": "CountryRegion", - "locInfoColumn": "Country", - "latitude": "Latitude", - "longitude": "Longitude", - "sizeSettings": "Count", - "sizeAggregation": "Sum", - "labelSettings": "Country", - "legendMetric": "Country", - "legendAggregation": "Count", - "itemColorSettings": { - "nodeColorField": "Count", - "colorAggregation": "Sum", - "type": "heatmap", - "heatmapPalette": "turquoise" - } - } - }, - "name": "query - 0" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| where tolower(Action) == \"allow\" and DestinationWebCategories != '' // Filter for allowed traffic\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| extend firstCategory = tostring(split(DestinationWebCategories, ',')[0]) // Split and get the first category\r\n| summarize Count = count() by firstCategory\r\n| top 10 by Count\r\n", - "size": 2, - "title": "Top Allowed Web Categories", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "visualization": "piechart" - }, - "customWidth": "50", - "name": "query - 7" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| where tolower(Action) == \"block\" and DestinationWebCategories != '' // Filter for blocked traffic\r\n| extend firstCategory = tostring(split(DestinationWebCategories, ',')[0]) // Split and get the first category\r\n| summarize Count = count() by firstCategory\r\n| top 10 by Count\r\n", - "size": 3, - "title": "Top Blocked Web Categories", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "visualization": "piechart" - }, - "customWidth": "50", - "name": "query - 6" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "NetworkAccessTraffic \r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| where tolower(Action) == \"block\" and DestinationFqdn != '' // Filter for blocked traffic with non-empty Destination FQDN\r\n| summarize Count = count() by [\"Destination FQDN\"] = DestinationFqdn, [\"Destination Web Categories\"] = DestinationWebCategories, [\"Policy Name\"] = PolicyName\r\n| order by Count\r\n", - "size": 0, - "title": "Top Blocked Destinations", - "timeContextFromParameter": "TimeRange", - "showRefreshButton": true, - "showExportToExcel": true, - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "gridSettings": { - "formatters": [ - { - "columnMatch": "Count", - "formatter": 4, - "formatOptions": { - "palette": "blue" - } - } - ], - "rowLimit": 1000, - "filter": true, - "sortBy": [ - { - "itemKey": "$gen_bar_Count_3", - "sortOrder": 2 - } - ] - }, - "sortBy": [ - { - "itemKey": "$gen_bar_Count_3", - "sortOrder": 2 - } - ] - }, - "customWidth": "50", - "name": "query - 5" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where DestinationWebCategories in ({WebCategories}) or '*' in ({WebCategories})\r\n| where SentBytes > 0\r\n| where tolower(Action) != \"block\"\r\n| summarize \r\n Count = count(), \r\n [\"Sent Bytes\"] = sum(SentBytes), \r\n [\"Received Bytes\"] = sum(ReceivedBytes), \r\n [\"Total Bytes\"] = sum(ReceivedBytes + SentBytes) \r\n by [\"Destination FQDN\"] = DestinationFqdn\r\n| order by Count desc\r\n", - "size": 0, - "title": "Top Allowed Destinations", - "timeContextFromParameter": "TimeRange", - "showRefreshButton": true, - "showExportToExcel": true, - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "gridSettings": { - "formatters": [ - { - "columnMatch": "Count", - "formatter": 4, - "formatOptions": { - "palette": "magenta" - } - }, - { - "columnMatch": "Recived", - "formatter": 4, - "formatOptions": { - "palette": "turquoise" - } - }, - { - "columnMatch": "Total", - "formatter": 4, - "formatOptions": { - "palette": "pink" - } - }, - { - "columnMatch": "Sent", - "formatter": 4, - "formatOptions": { - "palette": "blue" - } - } - ], - "rowLimit": 1000, - "filter": true, - "sortBy": [ - { - "itemKey": "$gen_bar_Count_1", - "sortOrder": 2 - } - ] - }, - "sortBy": [ - { - "itemKey": "$gen_bar_Count_1", - "sortOrder": 2 - } - ] - }, - "customWidth": "50", - "name": "query - 1" - }, - { - "type": 3, - "content": { - "version": "KqlItem/1.0", - "query": "NetworkAccessTraffic\r\n| where UserPrincipalName in ({Users}) or '*' in ({Users})\r\n| where TransportProtocol != ''\r\n| summarize Count = count() by toupper(TransportProtocol)\r\n| top 10 by Count\r\n", - "size": 2, - "title": "Protocol Distribution", - "timeContextFromParameter": "TimeRange", - "queryType": 0, - "resourceType": "microsoft.operationalinsights/workspaces", - "crossComponentResources": [ - "{LogAnalyticWorkspace}" - ], - "visualization": "piechart" - }, - "customWidth": "50", - "name": "query - 3" - } - ] - }, - "conditionalVisibility": { - "parameterName": "tabSel", - "comparison": "isEqualTo", - "value": "Overview" - }, - "name": "group - 4" - } - ], - "fallbackResourceIds": [ - "Global Secure Access" - ], - "fromTemplateId": "GSA Network Traffic Insights", - "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json" -} \ No newline at end of file From 60cdacc0b62d7f0c76cd6f68f4add9ca500c1316 Mon Sep 17 00:00:00 2001 From: v-shukore Date: Wed, 9 Oct 2024 10:15:46 +0530 Subject: [PATCH 26/27] Update ReleaseNotes.md --- Solutions/Global Secure Access/ReleaseNotes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Solutions/Global Secure Access/ReleaseNotes.md b/Solutions/Global Secure Access/ReleaseNotes.md index 15e70717f1..c865104afe 100644 --- a/Solutions/Global Secure Access/ReleaseNotes.md +++ b/Solutions/Global Secure Access/ReleaseNotes.md @@ -1,3 +1,3 @@ -| **Version** | **Date Modified (DD-MM-YYYY)** | **Change History** | -|-------------|--------------------------------|-----------------------------------------------------------------------------------------| -| 3.0.0 | 05-09-2024 | Initial Solution release | +| **Version** | **Date Modified (DD-MM-YYYY)** | **Change History** | +|-------------|--------------------------------|------------------------------------------------------------------------------------------| +| 3.0.0 | 05-09-2024 | Initial Solution release | From f36fc6622d3bf4bc1362a177752efd71fc59ccd5 Mon Sep 17 00:00:00 2001 From: v-shukore Date: Thu, 10 Oct 2024 10:36:26 +0530 Subject: [PATCH 27/27] Update Solution_GlobalSecureAccess.json --- .../Global Secure Access/Data/Solution_GlobalSecureAccess.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Solutions/Global Secure Access/Data/Solution_GlobalSecureAccess.json b/Solutions/Global Secure Access/Data/Solution_GlobalSecureAccess.json index d1978bff0b..abe547db91 100644 --- a/Solutions/Global Secure Access/Data/Solution_GlobalSecureAccess.json +++ b/Solutions/Global Secure Access/Data/Solution_GlobalSecureAccess.json @@ -55,7 +55,7 @@ "Hunting Queries/WindowsReservedFileNamesOnOfficeFileServices.yaml" ], "BasePath": "C:\\git\\Azure-Sentinel\\Azure-Sentinel\\Solutions\\Global Secure Access", - "Version": "3.0.1", + "Version": "3.0.0", "Metadata": "SolutionMetadata.json", "TemplateSpec": true, "StaticDataConnectorIds": [

ā€¢ Review the solution Release Notes