Skip to content

Commit 27ebc38

Browse files
authored
Merge pull request #11187 from Azure/v-prasadboke-GSA_new
Adding GSA Solution with new ID's for Analytic Rules
2 parents 21ed3ea + eaec28c commit 27ebc38

File tree

49 files changed

+8834
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+8834
-0
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
id: 4c9f0a9e-44d7-4c9b-b7f0-f6a6e0d8f8fa
2+
name: Detect Connections Outside Operational Hours
3+
description: This query identifies connections that occur outside of the defined operational hours. It helps in monitoring and flagging any unusual activity that may occur during non-business hours, indicating potential security concerns or policy violations.
4+
severity: High
5+
status: Available
6+
requiredDataConnectors:
7+
- connectorId: AzureActiveDirectory
8+
dataTypes:
9+
- EnrichedMicrosoft365AuditLogs
10+
queryFrequency: 1h
11+
queryPeriod: 24h
12+
triggerOperator: gt
13+
triggerThreshold: 0
14+
tactics:
15+
- InitialAccess
16+
relevantTechniques:
17+
- T1078
18+
- T1133
19+
query: |
20+
let starttime = todatetime('{{StartTimeISO}}');
21+
let endtime = todatetime('{{EndTimeISO}}');
22+
let operational_start_hour = 8; // Start of operational hours (8 AM)
23+
let operational_end_hour = 18; // End of operational hours (6 PM)
24+
NetworkAccessTraffic
25+
| where TimeGenerated between(starttime .. endtime)
26+
| extend HourOfDay = datetime_part('hour', TimeGenerated)
27+
| where HourOfDay < operational_start_hour or HourOfDay >= operational_end_hour
28+
| project TimeGenerated, UserPrincipalName, SourceIp, DestinationIp, DestinationPort, Action, DeviceId, DeviceOperatingSystem, ConnectionId
29+
| extend IPCustomEntity = SourceIp, AccountCustomEntity = UserPrincipalName
30+
entityMappings:
31+
- entityType: Account
32+
fieldMappings:
33+
- identifier: Name
34+
columnName: AccountCustomEntity
35+
- entityType: IP
36+
fieldMappings:
37+
- identifier: Address
38+
columnName: IPCustomEntity
39+
version: 1.0.0
40+
kind: Scheduled
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
id: 57abf863-1c1e-46c6-85b2-35370b712c1e
2+
name: Detect IP Address Changes and Overlapping Sessions
3+
description: |
4+
This query identifies network sessions based on DeviceId and UserPrincipalName, then checks for changed IP addresses and overlapping session times.
5+
severity: High
6+
status: Available
7+
requiredDataConnectors:
8+
- connectorId: AzureActiveDirectory
9+
dataTypes:
10+
- EnrichedMicrosoft365AuditLogs
11+
queryFrequency: 1h
12+
queryPeriod: 24h
13+
triggerOperator: gt
14+
triggerThreshold: 0
15+
tactics:
16+
- InitialAccess
17+
relevantTechniques:
18+
- T1078
19+
- T1133
20+
query: |
21+
// Identify sessions
22+
let sessions =
23+
NetworkAccessTraffic
24+
| summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), SourceIps = make_set(SourceIp) by DeviceId, UserPrincipalName, SessionId
25+
| sort by StartTime asc;
26+
// Check for changed IP addresses and overlapping session times
27+
sessions
28+
| extend PreviousSourceIps = prev(SourceIps, 1)
29+
| extend PreviousEndTime = prev(EndTime, 1)
30+
| extend PreviousDeviceId = prev(DeviceId, 1)
31+
| extend PreviousUserPrincipalName = prev(UserPrincipalName, 1)
32+
| where DeviceId == PreviousDeviceId and UserPrincipalName == PreviousUserPrincipalName
33+
| where set_difference(SourceIps, PreviousSourceIps) != dynamic([]) // Check if the current and previous IP sets differ
34+
| where PreviousEndTime > StartTime // Check for overlapping session times
35+
| project DeviceId, UserPrincipalName, SourceIps, PreviousSourceIps, StartTime, EndTime, PreviousEndTime
36+
| extend IPCustomEntity = tostring(array_slice(SourceIps, 0, 1)[0]), PreviousIPCustomEntity = tostring(array_slice(PreviousSourceIps, 0, 1)[0]), AccountCustomEntity = UserPrincipalName
37+
entityMappings:
38+
- entityType: Account
39+
fieldMappings:
40+
- identifier: Name
41+
columnName: AccountCustomEntity
42+
- entityType: IP
43+
fieldMappings:
44+
- identifier: Address
45+
columnName: IPCustomEntity
46+
version: 1.0.0
47+
kind: Scheduled
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
id: 4d38f80f-6b7d-4a1f-aeaf-e38df637e6ac
2+
name: Accessed files shared by temporary external user
3+
description: |
4+
'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.'
5+
severity: Low
6+
status: Available
7+
requiredDataConnectors:
8+
- connectorId: AzureActiveDirectory
9+
dataTypes:
10+
- EnrichedMicrosoft365AuditLogs
11+
queryFrequency: 1h
12+
queryPeriod: 1h
13+
triggerOperator: gt
14+
triggerThreshold: 0
15+
tactics:
16+
- InitialAccess
17+
relevantTechniques:
18+
- T1566
19+
query: |
20+
let fileAccessThreshold = 10;
21+
EnrichedMicrosoft365AuditLogs
22+
| where Workload =~ "MicrosoftTeams"
23+
| where Operation =~ "MemberAdded"
24+
| extend MemberAdded = tostring(parse_json(tostring(AdditionalProperties)).Members[0].UPN)
25+
| where MemberAdded contains "#EXT#"
26+
| project TimeAdded = TimeGenerated, Operation, MemberAdded, UserWhoAdded = UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)
27+
| join kind=inner (
28+
EnrichedMicrosoft365AuditLogs
29+
| where Workload =~ "MicrosoftTeams"
30+
| where Operation =~ "MemberRemoved"
31+
| extend MemberAdded = tostring(parse_json(tostring(AdditionalProperties)).Members[0].UPN)
32+
| where MemberAdded contains "#EXT#"
33+
| project TimeDeleted = TimeGenerated, Operation, MemberAdded, UserWhoDeleted = UserId, TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)
34+
) on MemberAdded, TeamName
35+
| where TimeDeleted > TimeAdded
36+
| join kind=inner (
37+
EnrichedMicrosoft365AuditLogs
38+
| where RecordType == "SharePointFileOperation"
39+
| where Operation == "FileUploaded"
40+
| extend MemberAdded = UserId, SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl), TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)
41+
| where SourceRelativeUrl has "Microsoft Teams Chat Files"
42+
| join kind=inner (
43+
EnrichedMicrosoft365AuditLogs
44+
| where RecordType == "SharePointFileOperation"
45+
| where Operation == "FileAccessed"
46+
| extend SourceRelativeUrl = tostring(parse_json(tostring(AdditionalProperties)).SourceRelativeUrl), TeamName = tostring(parse_json(tostring(AdditionalProperties)).TeamName)
47+
| where SourceRelativeUrl has "Microsoft Teams Chat Files"
48+
| summarize FileAccessCount = count() by ObjectId, TeamName
49+
| where FileAccessCount > fileAccessThreshold
50+
) on ObjectId, TeamName
51+
) on MemberAdded, TeamName
52+
| project-away MemberAdded1, MemberAdded2, ObjectId1, Operation1, Operation2
53+
| extend MemberAddedAccountName = tostring(split(MemberAdded, "@")[0]), MemberAddedAccountUPNSuffix = tostring(split(MemberAdded, "@")[1])
54+
| extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, "@")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, "@")[1])
55+
| extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, "@")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, "@")[1])
56+
entityMappings:
57+
- entityType: Account
58+
fieldMappings:
59+
- identifier: FullName
60+
columnName: MemberAdded
61+
- identifier: Name
62+
columnName: MemberAddedAccountName
63+
- identifier: UPNSuffix
64+
columnName: MemberAddedAccountUPNSuffix
65+
- entityType: Account
66+
fieldMappings:
67+
- identifier: FullName
68+
columnName: UserWhoAdded
69+
- identifier: Name
70+
columnName: UserWhoAddedAccountName
71+
- identifier: UPNSuffix
72+
columnName: UserWhoAddedAccountUPNSuffix
73+
- entityType: Account
74+
fieldMappings:
75+
- identifier: FullName
76+
columnName: UserWhoDeleted
77+
- identifier: Name
78+
columnName: UserWhoDeletedAccountName
79+
- identifier: UPNSuffix
80+
columnName: UserWhoDeletedAccountUPNSuffix
81+
- entityType: IP
82+
fieldMappings:
83+
- identifier: Address
84+
columnName: ClientIP
85+
version: 2.1.1
86+
kind: Scheduled
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
id: 1a8f1297-23a4-4f09-a20b-90af8fc3641a
2+
name: External User Added and Removed in Short Timeframe
3+
description: |
4+
This detection flags the occurrences of external user accounts that are added to a Team and then removed within one hour.
5+
severity: Low
6+
status: Available
7+
requiredDataConnectors:
8+
- connectorId: AzureActiveDirectory
9+
dataTypes:
10+
- EnrichedMicrosoft365AuditLogs
11+
queryFrequency: 1h
12+
queryPeriod: 1h
13+
triggerOperator: gt
14+
triggerThreshold: 0
15+
tactics:
16+
- Persistence
17+
relevantTechniques:
18+
- T1136
19+
query: |
20+
let TeamsAddDel = (Op:string){
21+
EnrichedMicrosoft365AuditLogs
22+
| where Workload =~ "MicrosoftTeams"
23+
| where Operation == Op
24+
| where tostring(AdditionalProperties.Members) has ("#EXT#")
25+
| mv-expand Members = parse_json(tostring(AdditionalProperties.Members))
26+
| extend UPN = tostring(Members.UPN)
27+
| where UPN has ("#EXT#")
28+
| project TimeGenerated, Operation, UPN, UserId, TeamName = tostring(AdditionalProperties.TeamName), ClientIP = SourceIp
29+
};
30+
let TeamsAdd = TeamsAddDel("MemberAdded")
31+
| project TimeAdded = TimeGenerated, Operation, MemberAdded = UPN, UserWhoAdded = UserId, TeamName, ClientIP;
32+
let TeamsDel = TeamsAddDel("MemberRemoved")
33+
| project TimeDeleted = TimeGenerated, Operation, MemberRemoved = UPN, UserWhoDeleted = UserId, TeamName, ClientIP;
34+
TeamsAdd
35+
| join kind = inner (TeamsDel) on $left.MemberAdded == $right.MemberRemoved
36+
| where TimeDeleted > TimeAdded
37+
| project TimeAdded, TimeDeleted, MemberAdded_Removed = MemberAdded, UserWhoAdded, UserWhoDeleted, TeamName
38+
| extend MemberAdded_RemovedAccountName = tostring(split(MemberAdded_Removed, "@")[0]), MemberAddedAccountUPNSuffix = tostring(split(MemberAdded_Removed, "@")[1])
39+
| extend UserWhoAddedAccountName = tostring(split(UserWhoAdded, "@")[0]), UserWhoAddedAccountUPNSuffix = tostring(split(UserWhoAdded, "@")[1])
40+
| extend UserWhoDeletedAccountName = tostring(split(UserWhoDeleted, "@")[0]), UserWhoDeletedAccountUPNSuffix = tostring(split(UserWhoDeleted, "@")[1])
41+
entityMappings:
42+
- entityType: Account
43+
fieldMappings:
44+
- identifier: FullName
45+
columnName: MemberAdded_Removed
46+
- identifier: Name
47+
columnName: MemberAdded_RemovedAccountName
48+
- identifier: UPNSuffix
49+
columnName: MemberAddedAccountUPNSuffix
50+
- entityType: Account
51+
fieldMappings:
52+
- identifier: FullName
53+
columnName: UserWhoAdded
54+
- identifier: Name
55+
columnName: UserWhoAddedAccountName
56+
- identifier: UPNSuffix
57+
columnName: UserWhoAddedAccountUPNSuffix
58+
- entityType: Account
59+
fieldMappings:
60+
- identifier: FullName
61+
columnName: UserWhoDeleted
62+
- identifier: Name
63+
columnName: UserWhoDeletedAccountName
64+
- identifier: UPNSuffix
65+
columnName: UserWhoDeletedAccountUPNSuffix
66+
- entityType: IP
67+
fieldMappings:
68+
- identifier: Address
69+
columnName: ClientIp
70+
version: 2.1.2
71+
kind: Scheduled
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
id: edcfc2e0-3134-434c-8074-9101c530d419
2+
name: Mail redirect via ExO transport rule
3+
description: |
4+
'Identifies when Exchange Online transport rule configured to forward emails.
5+
This could be an adversary mailbox configured to collect mail from multiple user accounts.'
6+
severity: Medium
7+
status: Available
8+
requiredDataConnectors:
9+
- connectorId: AzureActiveDirectory
10+
dataTypes:
11+
- EnrichedMicrosoft365AuditLogs
12+
queryFrequency: 1h
13+
queryPeriod: 1h
14+
triggerOperator: gt
15+
triggerThreshold: 0
16+
tactics:
17+
- Collection
18+
- Exfiltration
19+
relevantTechniques:
20+
- T1114
21+
- T1020
22+
query: |
23+
EnrichedMicrosoft365AuditLogs
24+
| where Workload == "Exchange"
25+
| where Operation in~ ("New-TransportRule", "Set-TransportRule")
26+
| mv-apply DynamicParameters = todynamic(tostring(AdditionalProperties.Parameters)) on (
27+
summarize ParsedParameters = make_bag(pack(tostring(DynamicParameters.Name), DynamicParameters.Value))
28+
)
29+
| extend RuleName = case(
30+
Operation =~ "Set-TransportRule", ObjectId, // Assuming ObjectId maps to what was previously OfficeObjectId
31+
Operation =~ "New-TransportRule", ParsedParameters.Name,
32+
"Unknown"
33+
)
34+
| mv-expand ExpandedParameters = todynamic(tostring(AdditionalProperties.Parameters))
35+
| where ExpandedParameters.Name in~ ("BlindCopyTo", "RedirectMessageTo") and isnotempty(ExpandedParameters.Value)
36+
| extend RedirectTo = ExpandedParameters.Value
37+
| extend ClientIPValues = extract_all(@'\[?(::ffff:)?(?P<IPAddress>(\d+\.\d+\.\d+\.\d+)|[^\]]+)\]?([-:](?P<Port>\d+))?', dynamic(["IPAddress", "Port"]), ClientIp)[0]
38+
| extend From = ParsedParameters.From
39+
| project TimeGenerated, RedirectTo, IPAddress = tostring(ClientIPValues[0]), Port = tostring(ClientIPValues[1]), UserId, From, Operation, RuleName, Parameters = tostring(AdditionalProperties.Parameters)
40+
| extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
41+
entityMappings:
42+
- entityType: Account
43+
fieldMappings:
44+
- identifier: FullName
45+
columnName: UserId
46+
- identifier: Name
47+
columnName: AccountName
48+
- identifier: UPNSuffix
49+
columnName: AccountUPNSuffix
50+
- entityType: IP
51+
fieldMappings:
52+
- identifier: Address
53+
columnName: IPAddress
54+
version: 2.0.4
55+
kind: Scheduled
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
id: a9c76c8d-f60d-49ec-9b1f-bdfee6db3807
2+
name: Malicious Inbox Rule
3+
description: |
4+
'Often times after the initial compromise the attackers create inbox rules to delete emails that contain certain keywords.
5+
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.
6+
Reference: https://www.reddit.com/r/sysadmin/comments/7kyp0a/recent_phishing_attempts_my_experience_and_what/'
7+
severity: Medium
8+
status: Available
9+
requiredDataConnectors:
10+
- connectorId: AzureActiveDirectory
11+
dataTypes:
12+
- EnrichedMicrosoft365AuditLogs
13+
queryFrequency: 1d
14+
queryPeriod: 1d
15+
triggerOperator: gt
16+
triggerThreshold: 0
17+
tactics:
18+
- Persistence
19+
- DefenseEvasion
20+
relevantTechniques:
21+
- T1098
22+
- T1078
23+
query: |
24+
let Keywords = dynamic(["helpdesk", "alert", "suspicious", "fake", "malicious", "phishing", "spam", "do not click", "do not open", "hijacked", "Fatal"]);
25+
EnrichedMicrosoft365AuditLogs
26+
| where Workload =~ "Exchange"
27+
| where Operation =~ "New-InboxRule" and (ResultStatus =~ "True" or ResultStatus =~ "Succeeded")
28+
| 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"
29+
| extend Events = parse_json(tostring(AdditionalProperties)).Parameters
30+
| extend SubjectContainsWords = tostring(Events.SubjectContainsWords), BodyContainsWords = tostring(Events.BodyContainsWords), SubjectOrBodyContainsWords = tostring(Events.SubjectOrBodyContainsWords)
31+
| where SubjectContainsWords has_any (Keywords) or BodyContainsWords has_any (Keywords) or SubjectOrBodyContainsWords has_any (Keywords)
32+
| extend ClientIPAddress = case(ClientIp has ".", tostring(split(ClientIp, ":")[0]), ClientIp has "[", tostring(trim_start(@'[[]',tostring(split(ClientIp, "]")[0]))), ClientIp)
33+
| extend Keyword = iff(isnotempty(SubjectContainsWords), SubjectContainsWords, (iff(isnotempty(BodyContainsWords), BodyContainsWords, SubjectOrBodyContainsWords)))
34+
| extend RuleDetail = case(ObjectId contains '/', tostring(split(ObjectId, '/')[-1]), tostring(split(ObjectId, '\\')[-1]))
35+
| summarize count(), StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated) by Operation, UserId, ClientIPAddress, ResultStatus, Keyword, ObjectId, RuleDetail
36+
| extend AccountName = tostring(split(UserId, "@")[0]), AccountUPNSuffix = tostring(split(UserId, "@")[1])
37+
38+
entityMappings:
39+
- entityType: Account
40+
fieldMappings:
41+
- identifier: FullName
42+
columnName: UserId
43+
- identifier: Name
44+
columnName: AccountName
45+
- identifier: UPNSuffix
46+
columnName: AccountUPNSuffix
47+
- entityType: Host
48+
fieldMappings:
49+
- identifier: FullName
50+
columnName: OriginatingServer
51+
- entityType: IP
52+
fieldMappings:
53+
- identifier: Address
54+
columnName: ClientIPAddress
55+
version: 2.0.4
56+
kind: Scheduled

0 commit comments

Comments
 (0)