Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Office rules and hunting queries #11207

Merged
merged 27 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -8,6 +8,9 @@ requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- EnrichedMicrosoft365AuditLogs
- connectorId: Office365
dataTypes:
- OfficeActivity (Teams)
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
Expand All @@ -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:
Expand Down Expand Up @@ -82,5 +125,5 @@ entityMappings:
fieldMappings:
- identifier: Address
columnName: ClientIP
version: 2.1.2
version: 2.1.3
kind: Scheduled
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -8,6 +8,9 @@ requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- EnrichedMicrosoft365AuditLogs
- connectorId: Office365
dataTypes:
- OfficeActivity (Teams)
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
Expand All @@ -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 TeamsDelOffice = TeamsAddDelOffice("MemberRemoved")
| project TimeDeleted = TimeGenerated, Operation, MemberRemoved = UPN, UserWhoDeleted = UserId, TeamName, ClientIP;
let TeamsAddEnriched = TeamsAddDelEnriched("MemberAdded")
| project TimeAdded = TimeGenerated, Operation, MemberAdded = UPN, UserWhoAdded = UserId, TeamName, ClientIP;
let TeamsDel = TeamsAddDel("MemberRemoved")
let TeamsDelEnriched = TeamsAddDelEnriched("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 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:
Expand All @@ -46,7 +69,7 @@ entityMappings:
- identifier: Name
columnName: MemberAdded_RemovedAccountName
- identifier: UPNSuffix
columnName: MemberAddedAccountUPNSuffix
columnName: MemberAdded_RemovedAccountUPNSuffix
- entityType: Account
fieldMappings:
- identifier: FullName
Expand All @@ -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
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -20,24 +23,53 @@ relevantTechniques:
- T1114
- T1020
query: |
EnrichedMicrosoft365AuditLogs
// OfficeActivity Query
let officeActivityQuery = OfficeActivity
| where OfficeWorkload == "Exchange"
| where Operation in~ ("New-TransportRule", "Set-TransportRule")
| mv-apply DynamicParameters = todynamic(Parameters) on (
summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))
)
| extend RuleName = case(
Operation =~ "Set-TransportRule", OfficeObjectId,
Operation =~ "New-TransportRule", ParsedParameters.Name,
"Unknown"
)
| mv-expand ExpandedParameters = todynamic(Parameters)
| where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value)
| extend RedirectTo = ExpandedParameters.Value
| extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P<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")
| mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on (
| extend AdditionalProps = parse_json(AdditionalProperties)
| mv-apply DynamicParameters = todynamic(AdditionalProps.Parameters) on (
summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))
)
| extend RuleName = case(
Operation =~ "Set-TransportRule", ObjectId, // Assuming ObjectId maps to what was previously OfficeObjectId
Operation =~ "Set-TransportRule", ObjectId,
Operation =~ "New-TransportRule", ParsedParameters.Name,
"Unknown"
)
| mv-expand ExpandedParameters = todynamic(tostring(AdditionalProperties.Parameters))
| mv-expand ExpandedParameters = todynamic(AdditionalProps.Parameters)
| where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value)
| extend RedirectTo = ExpandedParameters.Value
| extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P<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:
Expand All @@ -51,5 +83,5 @@ entityMappings:
fieldMappings:
- identifier: Address
columnName: IPAddress
version: 2.0.5
kind: Scheduled
version: 2.1.4
kind: Scheduled
Loading
Loading