Skip to content

Commit

Permalink
Merge pull request #9690 from Azure/origins/users/rahul/m365-validation
Browse files Browse the repository at this point in the history
Microsoft 365 - Bugfixes
  • Loading branch information
v-atulyadav authored Jan 4, 2024
2 parents fe60fd3 + 4fdc910 commit 5c1016b
Show file tree
Hide file tree
Showing 40 changed files with 1,318 additions and 1,184 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ requiredDataConnectors:
- connectorId: Office365
dataTypes:
- OfficeActivity (Teams)
- connectorId: Office365
dataTypes:
- OfficeActivity (SharePoint)
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
Expand Down Expand Up @@ -63,5 +66,5 @@ entityMappings:
fieldMappings:
- identifier: Address
columnName: ClientIP
version: 2.0.1
version: 2.0.2
kind: Scheduled
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ status: Available
requiredDataConnectors:
- connectorId: Office365
dataTypes:
- OfficeActivity
- OfficeActivity (Exchange)
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
Expand Down Expand Up @@ -45,12 +45,12 @@ query: |
// during the anomalyhour to analysts to conduct investigation or informed decisions.
TimeSeriesAlerts | where TimeGenerated > ago(2d)
// Join against base logs since specified timeframe to retrive records associated with the hour of anomoly
| join (
| join kind=innerunique (
OfficeActivity
| where TimeGenerated > ago(2d)
| extend DateHour = bin(TimeGenerated, 1h)
| where OfficeWorkload=~ "Exchange" and Operation =~ "MailItemsAccessed" and ResultStatus =~ "Succeeded"
| summarize HourlyCount=count(), TimeGeneratedMax = arg_max(TimeGenerated, *), IPAdressList = make_set(Client_IPAddress), SourceIPMax= arg_max(Client_IPAddress, *), ClientInfoStringList= make_set(ClientInfoString) by MailboxOwnerUPN, Logon_Type, TenantId, UserType, TimeGenerated = bin(TimeGenerated, 1h)
| summarize HourlyCount=count(), TimeGeneratedMax = arg_max(TimeGenerated, *), IPAdressList = make_set(Client_IPAddress, 1000), SourceIPMax= arg_max(Client_IPAddress, *), ClientInfoStringList= make_set(ClientInfoString, 1000) by MailboxOwnerUPN, Logon_Type, TenantId, UserType, TimeGenerated = bin(TimeGenerated, 1h)
| where HourlyCount > 25 // Only considering operations with more than 25 hourly count to reduce False Positivies
| order by HourlyCount desc
) on TimeGenerated
Expand All @@ -70,5 +70,5 @@ entityMappings:
fieldMappings:
- identifier: Address
columnName: ClientIP1
version: 2.0.2
version: 2.0.3
kind: Scheduled
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ status: Available
requiredDataConnectors:
- connectorId: Office365
dataTypes:
- OfficeActivity
- OfficeActivity (Exchange)
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
Expand Down Expand Up @@ -45,5 +45,5 @@ entityMappings:
fieldMappings:
- identifier: Address
columnName: IPAddress
version: 2.0.1
version: 2.0.2
kind: Scheduled
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ status: Available
requiredDataConnectors:
- connectorId: Office365
dataTypes:
- OfficeActivity
- OfficeActivity (Exchange)
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
Expand All @@ -23,7 +23,8 @@ relevantTechniques:
query: |
let Keywords = dynamic(["helpdesk", " alert", " suspicious", "fake", "malicious", "phishing", "spam", "do not click", "do not open", "hijacked", "Fatal"]);
OfficeActivity
| where Operation =~ "New-InboxRule"
| 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 '}'*
Expand Down Expand Up @@ -52,5 +53,5 @@ entityMappings:
fieldMappings:
- identifier: Address
columnName: ClientIPAddress
version: 2.0.2
version: 2.0.3
kind: Scheduled
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ query: |
OfficeActivity
| where OfficeWorkload =~ "MicrosoftTeams"
| where Operation =~ "TeamDeleted"
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), DeletedTeams = make_set(TeamName) by UserId
| 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])
entityMappings:
Expand All @@ -35,5 +35,5 @@ entityMappings:
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
version: 2.0.2
version: 2.0.3
kind: Scheduled
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ status: Available
requiredDataConnectors:
- connectorId: Office365
dataTypes:
- OfficeActivity
- OfficeActivity (Exchange)
queryFrequency: 1d
queryPeriod: 7d
triggerOperator: gt
Expand All @@ -27,7 +27,7 @@ query: |
| where OfficeWorkload =~ "Exchange"
//| 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)))
| 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", ""),
Expand All @@ -53,5 +53,5 @@ entityMappings:
fieldMappings:
- identifier: Address
columnName: ClientIP
version: 2.0.1
version: 2.0.2
kind: Scheduled
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ status: Available
requiredDataConnectors:
- connectorId: Office365
dataTypes:
- OfficeActivity
- OfficeActivity (SharePoint)
queryFrequency: 1d
queryPeriod: 8d
triggerOperator: gt
Expand Down Expand Up @@ -39,18 +39,18 @@ query: |
OfficeActivity | where TimeGenerated between (ago(starttime) .. ago(endtime))
| where Operation =~ uploadOp
| where SourceFileExtension has_any (execExt)
| summarize SourceRelativeUrl = make_set(SourceRelativeUrl), UserId = make_set(UserId) , PrevSeenCount = count() by SourceFileName
| summarize SourceRelativeUrl = make_set(SourceRelativeUrl, 100000), UserId = make_set(UserId, 100000) , PrevSeenCount = count() by SourceFileName
// To exclude previous matches when only above a specific count, change threshold above and uncomment the line below
//| 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('@|\\.', '_',UserId))
| 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), StartTime = min(TimeGenerated), EndTime = max(TimeGenerated),
UserAgents = make_list(UserAgent), OfficeIds = make_list(OfficeId), SourceRelativeUrls = make_list(SourceRelativeUrl), FileNames = make_list(SourceFileName)
| 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(SourceFileName, 100000)
by OfficeWorkload, RecordType, Operation, UserType, UserKey, UserId, ClientIP, Site_Url, SiteUrlUserFolder, UserIdUserFolderFormat, UserIdDiffThanUserFolder
| extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
entityMappings:
Expand All @@ -72,5 +72,5 @@ entityMappings:
fieldMappings:
- identifier: Name
columnName: FileNames
version: 2.0.3
version: 2.0.4
kind: Scheduled
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ relevantTechniques:
query: |
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 (w3wp)', 'devilfish-applicationaccount') and Operation in~ ( "Add-MailboxPermission", "Set-Mailbox"))
and not(UserId has_any ('NT AUTHORITY\\SYSTEM (Microsoft.Exchange.ServiceHost)', 'NT AUTHORITY\\SYSTEM (Microsoft.Exchange.AdminApi.NetCore)', 'NT AUTHORITY\\SYSTEM (w3wp)', 'devilfish-applicationaccount') and Operation in~ ( "Add-MailboxPermission", "Set-Mailbox"))
| extend ClientIPOnly = tostring(extract_all(@'\[?(::ffff:)?(?P<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?', dynamic(["IPAddress"]), ClientIP)[0][0])
| extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
entityMappings:
Expand All @@ -39,5 +39,5 @@ entityMappings:
fieldMappings:
- identifier: AppId
columnName: AppId
version: 2.0.2
version: 2.0.3
kind: Scheduled
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
id: 194dd92e-d6e7-4249-85a5-273350a7f5ce
name: Exchange AuditLog disabled
name: 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.'
Expand All @@ -8,7 +8,7 @@ status: Available
requiredDataConnectors:
- connectorId: Office365
dataTypes:
- OfficeActivity
- OfficeActivity (Exchange)
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
Expand All @@ -19,23 +19,29 @@ relevantTechniques:
- T1562
query: |
OfficeActivity
| where OfficeWorkload =~ "Exchange"
| where UserType in~ ("Admin","DcAdmin")
// Only admin or global-admin can disable audit logging
| where Operation =~ "Set-AdminAuditLogConfig"
| extend AdminAuditLogEnabledValue = tostring(parse_json(tostring(parse_json(tostring(array_slice(parse_json(Parameters),3,3)))[0])).Value)
| where AdminAuditLogEnabledValue =~ "False"
| summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), OperationCount = count() by Operation, UserType, UserId, ClientIP, ResultStatus, Parameters, AdminAuditLogEnabledValue
| extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
| 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]), '')
entityMappings:
- entityType: Account
fieldMappings:
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- identifier: NTDomain
columnName: AccountNTDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: ClientIP
version: 2.0.2
version: 2.0.3
kind: Scheduled
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
id: fbd72eb8-087e-466b-bd54-1ca6ea08c6d3
name: Office policy tampering
name: Office Policy Tampering
description: |
'Identifies if any tampering is done to either auditlog, ATP Safelink, SafeAttachment, AntiPhish or Dlp policy.
An adversary may use this technique to evade detection or avoid other policy based defenses.
Expand All @@ -9,7 +9,7 @@ status: Available
requiredDataConnectors:
- connectorId: Office365
dataTypes:
- OfficeActivity
- OfficeActivity (Exchange)
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
Expand All @@ -26,7 +26,7 @@ query: |
//| where Operation startswith "Remove-" or Operation startswith "Disable-"
| 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);
| summarize make_set(Operation, 500);
OfficeActivity
// Only admin or global-admin can disable/remove policy
| where RecordType =~ "ExchangeAdmin"
Expand Down Expand Up @@ -56,5 +56,5 @@ entityMappings:
fieldMappings:
- identifier: Address
columnName: ClientIP
version: 2.0.1
version: 2.0.2
kind: Scheduled
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ severity: Medium
requiredDataConnectors:
- connectorId: Office365
dataTypes:
- OfficeActivity
- OfficeActivity (SharePoint)
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
Expand All @@ -22,7 +22,7 @@ query: |
let threshold = 5000;
OfficeActivity
| where EventSource == "SharePoint" and OfficeWorkload has_any("SharePoint", "OneDrive") and Operation has_any ("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded")
| summarize count_distinct_OfficeObjectId=dcount(OfficeObjectId), fileslist=make_set(OfficeObjectId) by UserId,ClientIP,bin(TimeGenerated, 15m)
| summarize count_distinct_OfficeObjectId=dcount(OfficeObjectId), fileslist=make_set(OfficeObjectId, 10000) by UserId,ClientIP,bin(TimeGenerated, 15m)
| where count_distinct_OfficeObjectId >= threshold
| extend FileSample = iff(array_length(fileslist) == 1, tostring(fileslist[0]), strcat("SeeFilesListField","_", tostring(hash(tostring(fileslist)))))
| extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
Expand Down Expand Up @@ -55,5 +55,5 @@ incidentConfiguration:
- Account
groupByAlertDetails: []
groupByCustomDetails: []
version: 1.0.1
version: 1.0.2
kind: Scheduled
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ severity: Medium
requiredDataConnectors:
- connectorId: Office365
dataTypes:
- OfficeActivity
- OfficeActivity (SharePoint)
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
Expand All @@ -22,7 +22,7 @@ query: |
let threshold = 500;
OfficeActivity
| where EventSource == "SharePoint" and OfficeWorkload has_any("SharePoint", "OneDrive") and Operation has_any ("FileDownloaded", "FileSyncDownloadedFull", "FileSyncUploadedFull", "FileUploaded")
| summarize count_distinct_SourceRelativeUrl=dcount(SourceRelativeUrl), dirlist=make_set(SourceRelativeUrl) by UserId,ClientIP,UserAgent,bin(TimeGenerated, 15m)
| summarize count_distinct_SourceRelativeUrl=dcount(SourceRelativeUrl), dirlist=make_set(SourceRelativeUrl, 10000) by UserId,ClientIP,UserAgent,bin(TimeGenerated, 15m)
| where count_distinct_SourceRelativeUrl >= threshold
| extend DirSample = iff(array_length(dirlist) == 1, tostring(dirlist[0]), strcat("SeeDirListField","_", tostring(hash(tostring(dirlist)))))
| extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
Expand Down Expand Up @@ -55,5 +55,5 @@ incidentConfiguration:
- Account
groupByAlertDetails: []
groupByCustomDetails: []
version: 1.0.1
version: 1.0.2
kind: Scheduled
1 change: 0 additions & 1 deletion Solutions/Microsoft 365/Data/Solution_Office365.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
"Analytic Rules/RareOfficeOperations.yaml",
"Analytic Rules/SharePoint_Downloads_byNewIP.yaml",
"Analytic Rules/SharePoint_Downloads_byNewUserAgent.yaml",
"Analytic Rules/ForestBlizzardCredHarvesting.yaml",
"Analytic Rules/exchange_auditlogdisabled.yaml",
"Analytic Rules/office_policytampering.yaml"
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
id: 271e8881-3044-4332-a5f4-42264c2e0315
name: Anomalous access to other user's mailboxes
name: Anomalous access to other users' mailboxes
description: |
'Looks for users accessing multiple other user's mailboxes or accessing multiple folders in another users mailbox'
'Looks for users accessing multiple other users' mailboxes or accessing multiple folders in another users mailbox.'
requiredDataConnectors:
- connectorId: Office365
dataTypes:
- OfficeActivity
- OfficeActivity (Exchange)
tactics:
- Collection
relevantTechniques:
Expand All @@ -14,7 +14,6 @@ tags:
- Solorigate
- NOBELIUM
query: |
let starttime = todatetime('{{StartTimeISO}}');
let endtime = todatetime('{{EndTimeISO}}');
let lookback = totimespan((endtime-starttime)*2);
Expand All @@ -38,17 +37,21 @@ query: |
| 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), make_set(folders), make_set(ClientInfoString), make_set(ClientIP), make_set(MailboxGuid), make_set(MailboxOwnerUPN) by UserId
| summarize StartTime=max(TimeGenerated), EndTime=min(TimeGenerated), make_set(folders, 100000), make_set(ClientInfoString, 100000), make_set(ClientIP, 100000), make_set(MailboxGuid, 100000), 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 timestamp = StartTime, AccountCustomEntity = UserId
| extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
| extend Account_0_Name = AccountName
| extend Account_0_UPNSuffix = AccountUPNSuffix
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: AccountCustomEntity
version: 2.0.0
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
version: 2.0.1
Loading

0 comments on commit 5c1016b

Please sign in to comment.