Merge pull request #11207 from Azure/GSA-New

Fix Office rules and hunting queries
This commit is contained in:
v-atulyadav 2024-10-10 13:41:01 +05:30 коммит произвёл GitHub
Родитель e1f492123f f36fc6622d
Коммит 37682164e4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
43 изменённых файлов: 3597 добавлений и 2555 удалений

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

@ -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
@ -8,6 +8,9 @@ requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- EnrichedMicrosoft365AuditLogs
- connectorId: Office365
dataTypes:
- OfficeActivity (Teams)
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
@ -17,42 +20,82 @@ tactics:
relevantTechniques:
- T1566
query: |
let fileAccessThreshold = 10;
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-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])
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)
| 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:
@ -82,5 +125,5 @@ entityMappings:
fieldMappings:
- identifier: Address
columnName: ClientIP
version: 2.1.2
version: 2.1.3
kind: Scheduled

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

@ -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
@ -8,6 +8,9 @@ requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- EnrichedMicrosoft365AuditLogs
- connectorId: Office365
dataTypes:
- OfficeActivity (Teams)
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
@ -17,27 +20,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(parse_json(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:
@ -46,7 +69,7 @@ entityMappings:
- identifier: Name
columnName: MemberAdded_RemovedAccountName
- identifier: UPNSuffix
columnName: MemberAddedAccountUPNSuffix
columnName: MemberAdded_RemovedAccountUPNSuffix
- entityType: Account
fieldMappings:
- identifier: FullName
@ -66,6 +89,6 @@ entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIp
version: 2.1.3
kind: Scheduled
columnName: ClientIP
version: 2.1.4
kind: Scheduled

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

@ -1,11 +1,14 @@
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.'
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: Office365
dataTypes:
- OfficeActivity (Exchange)
- connectorId: AzureActiveDirectory
dataTypes:
- EnrichedMicrosoft365AuditLogs
@ -20,24 +23,53 @@ relevantTechniques:
- T1114
- T1020
query: |
EnrichedMicrosoft365AuditLogs
| where Workload == "Exchange"
// OfficeActivity Query
let officeActivityQuery = OfficeActivity
| where OfficeWorkload == "Exchange"
| where Operation in~ ("New-TransportRule", "Set-TransportRule")
| mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on (
| mv-apply DynamicParameters = todynamic(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 =~ "Set-TransportRule", OfficeObjectId,
Operation =~ "New-TransportRule", ParsedParameters.Name,
"Unknown"
)
| mv-expand ExpandedParameters = todynamic(tostring(AdditionalProperties.Parameters))
| 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<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\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]);
// EnrichedMicrosoft365AuditLogs Query
let enrichedLogsQuery = EnrichedMicrosoft365AuditLogs
| where Workload == "Exchange"
| where Operation in~ ("New-TransportRule", "Set-TransportRule")
| 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,
Operation =~ "New-TransportRule", ParsedParameters.Name,
"Unknown"
)
| 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<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\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 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
| project TimeGenerated, RedirectTo, IPAddress, Port, UserId, From, Operation, RuleName, Parameters, AccountName, AccountUPNSuffix
| order by TimeGenerated desc;
entityMappings:
- entityType: Account
fieldMappings:
@ -51,5 +83,5 @@ entityMappings:
fieldMappings:
- identifier: Address
columnName: IPAddress
version: 2.0.5
kind: Scheduled
version: 2.1.4
kind: Scheduled

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

@ -1,15 +1,18 @@
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.
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:
- connectorId: AzureActiveDirectory
dataTypes:
- EnrichedMicrosoft365AuditLogs
- connectorId: Office365
dataTypes:
- OfficeActivity (Exchange)
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
@ -21,20 +24,70 @@ 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]),
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
| 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 Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, (iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords)))
| extend RuleDetail = case(ObjectId contains '/', tostring(split(ObjectId, '/')[-1]), tostring(split(ObjectId, '\\')[-1]))
| 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]),
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])
| 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:
@ -44,13 +97,9 @@ entityMappings:
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: OriginatingServer
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIPAddress
version: 2.0.5
version: 2.0.6
kind: Scheduled

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

@ -1,14 +1,17 @@
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.'
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:
- connectorId: AzureActiveDirectory
dataTypes:
- EnrichedMicrosoft365AuditLogs
- connectorId: Office365
dataTypes:
- OfficeActivity (Teams)
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
@ -19,13 +22,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 +54,5 @@ entityMappings:
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
version: 2.0.5
kind: Scheduled
version: 2.0.6
kind: Scheduled

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

@ -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.
@ -9,6 +9,9 @@ requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- EnrichedMicrosoft365AuditLogs
- connectorId: Office365
dataTypes:
- OfficeActivity (Exchange)
queryFrequency: 1d
queryPeriod: 7d
triggerOperator: gt
@ -20,30 +23,64 @@ relevantTechniques:
- T1114
- T1020
query: |
// Set query parameters
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<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\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])
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<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\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<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\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 +94,5 @@ entityMappings:
fieldMappings:
- identifier: Address
columnName: ClientIP
version: 2.0.4
version: 2.0.5
kind: Scheduled

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

@ -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.
@ -11,6 +11,9 @@ requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- EnrichedMicrosoft365AuditLogs
- connectorId: Office365
dataTypes:
- OfficeActivity (SharePoint)
queryFrequency: 1d
queryPeriod: 8d
triggerOperator: gt
@ -22,39 +25,79 @@ relevantTechniques:
- T1105
- T1570
query: |
let threshold = 2;
let uploadOp = 'FileUploaded';
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))
| 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)
) 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])
// 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:
@ -67,7 +110,7 @@ entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIp
columnName: ClientIP
- entityType: URL
fieldMappings:
- identifier: Url
@ -76,5 +119,5 @@ entityMappings:
fieldMappings:
- identifier: Name
columnName: FileNames
version: 2.0.6
version: 2.0.7
kind: Scheduled

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

@ -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
@ -8,6 +8,9 @@ requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- EnrichedMicrosoft365AuditLogs
- connectorId: Office365
dataTypes:
- OfficeActivity
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
@ -19,11 +22,32 @@ 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<IPAddress>(\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<IPAddress>(\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<IPAddress>(\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:
@ -37,9 +61,5 @@ entityMappings:
fieldMappings:
- identifier: Address
columnName: ClientIPOnly
- entityType: CloudApplication
fieldMappings:
- identifier: AppId
columnName: AppId
version: 2.0.6
version: 2.0.7
kind: Scheduled

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

@ -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
@ -8,6 +8,9 @@ requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- EnrichedMicrosoft365AuditLogs
- connectorId: Office365
dataTypes:
- OfficeActivity (SharePoint)
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
@ -17,40 +20,62 @@ 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:
@ -63,10 +88,10 @@ entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIp
columnName: ClientIP
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Site_Url
version: 2.0.5
version: 2.0.6
kind: Scheduled

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

@ -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
@ -8,6 +8,9 @@ requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- EnrichedMicrosoft365AuditLogs
- connectorId: Office365
dataTypes:
- OfficeActivity (SharePoint)
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
@ -17,52 +20,92 @@ 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 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
| where TimeGenerated between (ago(starttime) .. ago(endtime))
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);
// 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)
| extend ClientIP = ClientIp
| where isnotempty(UserAgent)
| where UserAgent in (FrequentUA)
| 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
| 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;
// Filter and format results for specific user behavior analysis
UserBehaviorAnalysis
let UserBehaviorAnalysisEnriched = UserBaseLineEnriched
| join kind=inner (RecentActivityEnriched) on UserId, Operation, Site_Url
| extend Deviation = abs(UserAgentSeenCount - AvgCount) / AvgCount;
// 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
| 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
@ -76,10 +119,10 @@ entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIp
columnName: ClientIP
- entityType: URL
fieldMappings:
- identifier: Url
columnName: Site_Url
version: 2.2.5
version: 2.2.6
kind: Scheduled

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

@ -1,13 +1,16 @@
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.'
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:
- connectorId: AzureActiveDirectory
dataTypes:
- EnrichedMicrosoft365AuditLogs
- connectorId: Office365
dataTypes:
- OfficeActivity (Exchange)
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
@ -17,17 +20,42 @@ 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 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
fieldMappings:
@ -37,13 +65,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.7
kind: Scheduled
version: 2.0.8
kind: Scheduled

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

@ -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.
@ -10,6 +10,9 @@ requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- EnrichedMicrosoft365AuditLogs
- connectorId: Office365
dataTypes:
- OfficeActivity (Exchange)
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
@ -21,27 +24,69 @@ 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 +100,5 @@ entityMappings:
fieldMappings:
- identifier: Address
columnName: ClientIP
version: 2.0.4
version: 2.0.6
kind: Scheduled

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

@ -1,13 +1,17 @@
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.
'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
@ -18,12 +22,38 @@ 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")
| extend ClientIP = ClientIp
| summarize TransferCount = dcount(ObjectId), fileslist = make_set(ObjectId, 10000)
by UserId, ClientIP, bin(TimeGenerated, 15m)
| 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 Operation has_any("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded")
| summarize TransferCount = dcount(OfficeObjectId), fileslist = make_set(OfficeObjectId, 10000)
by UserId, ClientIP, bin(TimeGenerated, 15m)
| 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, TransferCount, fileslist
| order by TimeGenerated desc
entityMappings:
- entityType: Account
fieldMappings:
@ -36,24 +66,22 @@ entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIp
columnName: ClientIP
- entityType: File
fieldMappings:
- identifier: Name
columnName: FileSample
customDetails:
TransferCount: count_distinct_ObjectId
TransferCount: TransferCount
FilesList: fileslist
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
lookbackDuration: PT5H
matchingMethod: Selected
groupByEntities:
- Account
groupByAlertDetails: []
groupByCustomDetails: []
version: 1.0.5
kind: Scheduled
version: 1.0.6
kind: Scheduled

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

@ -1,13 +1,16 @@
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.
'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
@ -17,15 +20,44 @@ 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 = 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:
@ -38,24 +70,22 @@ 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
TransferCount: TransferCount
FilesList: fileslist
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: 5h
lookbackDuration: PT5H
matchingMethod: Selected
groupByEntities:
- Account
groupByAlertDetails: []
groupByCustomDetails: []
version: 1.0.5
kind: Scheduled
version: 2.0.8
kind: Scheduled

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

@ -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,31 +30,30 @@
"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.0.0",
"Metadata": "SolutionMetadata.json",

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

@ -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

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

@ -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

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

@ -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<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\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<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\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<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\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

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

@ -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:

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

@ -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

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

@ -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

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

@ -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<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\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<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\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<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\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

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

@ -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

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

@ -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

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

@ -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

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

@ -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

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

@ -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

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

@ -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

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

@ -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

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

@ -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

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

@ -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:

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

@ -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

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

@ -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

Двоичные данные
Solutions/Global Secure Access/Package/3.0.0.zip

Двоичный файл не отображается.

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

@ -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,13 +332,13 @@
{
"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",
"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."
"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."
}
}
]
@ -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",
@ -440,178 +440,10 @@
{
"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": "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": "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": "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": "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": "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": "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": "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": "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": "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": "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": "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",
"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)"
@ -620,12 +452,124 @@
]
},
{
"name": "huntingquery15",
"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": "huntingquery15-text",
"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)"
@ -634,12 +578,12 @@
]
},
{
"name": "huntingquery16",
"name": "huntingquery12",
"type": "Microsoft.Common.Section",
"label": "SharePointFileOperation via previously unseen IPs",
"label": "GSA Enriched Office 365 - SharePointFileOperation via previously unseen IPs",
"elements": [
{
"name": "huntingquery16-text",
"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)"
@ -648,12 +592,12 @@
]
},
{
"name": "huntingquery17",
"name": "huntingquery13",
"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",
"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)"
@ -662,12 +606,26 @@
]
},
{
"name": "huntingquery18",
"name": "huntingquery14",
"type": "Microsoft.Common.Section",
"label": "Non-owner mailbox login activity",
"label": "GSA Enriched Office 365 - New Windows Reserved Filenames staged on Office file services",
"elements": [
{
"name": "huntingquery18-text",
"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)"
@ -676,12 +634,26 @@
]
},
{
"name": "huntingquery19",
"name": "huntingquery16",
"type": "Microsoft.Common.Section",
"label": "PowerShell or non-browser mailbox login activity",
"label": "GSA Enriched Office 365 - Office Mail Forwarding - Hunting Version",
"elements": [
{
"name": "huntingquery19-text",
"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)"
@ -690,12 +662,12 @@
]
},
{
"name": "huntingquery20",
"name": "huntingquery18",
"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",
"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)"
@ -703,16 +675,44 @@
}
]
},
{
"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": "Multiple Users Email Forwarded to Same Destination",
"label": "GSA Enriched Office 365 - Windows Reserved Filenames Staged on Office File Services",
"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)"
"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)"
}
}
]

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

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

@ -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 |

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -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,26 +628,15 @@
"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",
"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",
@ -348,12 +646,61 @@
"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 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": [
{
@ -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"
}

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

@ -55,5 +55,5 @@ incidentConfiguration:
- Account
groupByAlertDetails: []
groupByCustomDetails: []
version: 1.0.5
version: 1.0.6
kind: Scheduled

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

@ -1,418 +0,0 @@
{
"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 - 1"
},
{
"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 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",
"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",
"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"
}
],
"fallbackResourceIds": [],
"$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json"
}

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

@ -1,421 +0,0 @@
{
"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"
}
],
"fallbackResourceIds": [
],
"$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json"
}

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

@ -8017,8 +8017,8 @@
"GSATrafficLogsWhite.png",
"GSATrafficLogsBlack.png"
],
"version": "1.0.0",
"title": "Microsoft Global Secure Access Traffic Logs",
"version": "1.0.1",
"title": "Network Traffic Insights",
"templateRelativePath": "GSANetworkTraffic.json",
"subtitle": "",
"provider": "Microsoft"
@ -8035,8 +8035,8 @@
"GSAEnrichedLogsWhite.png",
"GSAEnrichedLogsBlack.png"
],
"version": "1.0.0",
"title": "Microsoft Global Secure Access Enriched M365 Logs",
"version": "1.0.1",
"title": "Enriched Microsoft 365 logs Workbook",
"templateRelativePath": "GSAM365EnrichedEvents.json",
"provider": "Microsoft"
},