This repository has been archived by the owner on Nov 16, 2023. It is now read-only.
/
MCAS - The Hunt.txt
225 lines (185 loc) · 12.3 KB
/
MCAS - The Hunt.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
print Topic = "l33tSpeak: Advanced hunting in Microsoft 365 Defender"
, Presenters = pack_array("Sebastien Molendijk, Michael Melone, Tali Ash")
, Company = "Microsoft"
, Date = todatetime("10 MAY 2021")
///////////////////////
// Working with the dynamic type
// ref: https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/scalar-data-types/dynamic
///////////////////////
// Dynamic is an object oriented format for storing structured data.
// Dynamics are usually converted from JSON strings using either todynamic() or parse_json() (same function)
// You can also convert XML into a dynamic by using parse_xml()
let JsonString = '{"hello": 1337, "world": ["wibble","wobble","wubble"]}';
print todynamic(JsonString)
// There are a number of ways you can interact with elements stored as dynamic() typed objects.
// Interacting with child elements
// - Column.Child
// - Column[“Child”]
let JsonString = '{"hello": 1337, "world": ["wibble","wobble","wubble"]}';
print x = todynamic(JsonString)
| extend hello = x.hello, world = x["world"]
// Interacting with lists \ arrays
// Column[ElementNumber]
let JsonString = '{"hello": 1337, "world": ["wibble","wobble","wubble"]}';
print x = todynamic(JsonString)
| extend hello = x.hello, world = x["world"]
| extend FirstElement = world[0], SecondElement = world[1]
// The information on the actor initiating the activities can be found in AccountObjectId and AccountDisplayName columns.
// To get the target account and the activities were performed we can extract information from RawEventData
CloudAppEvents
| where ActionType == "AddedToGroup"
| take 50
| project-reorder AccountObjectId, AccountDisplayName, RawEventData
CloudAppEvents
| where ActionType == "AddedToGroup"
| project Timestamp, Application, IPAddress, Actor = AccountDisplayName, AddedUser = RawEventData.TargetUserOrGroupName
CloudAppEvents
| where ActionType == "AddedToGroup"
| project Timestamp, Application, IPAddress, Actor = AccountDisplayName, AddedUser = RawEventData["TargetUserOrGroupName"]
//////////////////////////////
// pack_array()
// ref: https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/packarrayfunction
// Creates a dynamic array
//////////////////////////////
// Lists, sets, and arrays in KQL are stored as dynamics and can be created
// with functions such as pack_array()
print pack_array('foo','bar','baz')
// Note that you cannot simply compare dynamic elements in KQL. To do this,
// convert them back to another type using functions such as tostring() or toint()
let JsonDynamic = todynamic('{"hello": 1337, "world": ["wibble","wobble","wubble"]}');
print tostring(JsonDynamic.hello) == tostring(JsonDynamic['hello'])
////////////////////////
// bag_unpack()
// ref: https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/bag-unpackplugin
// Automatically unpacks the first level of a dynamic to a table
////////////////////////
// Another option is to use bag_unpack() to turn JSON data directly into a table.
// Note that bag_unpack() only processes the first level of JSON. If you have
// multiple nested JSON elements you may need multiple calls to the function.
let JsonDynamic = todynamic('{"hello": 1337, "world": ["wibble","wobble","wubble"]}');
print x = JsonDynamic
| evaluate bag_unpack(x)
/////////////////////////////
// mv-expand
// ref: https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/mvexpandoperator
// Multiplies elements in a dynamic array across a tabular dataset
/////////////////////////////
// You can also use functions such as mv-expand to parse elements in a dynamic
// across a table. This is very handy for efficiently analyzing lists of
// elements at scale
// Let’s put bag_unpack() and mv-expand together.
let JsonDynamic = todynamic('{"hello": 1337, "world": ["wibble","wobble","wubble"]}');
print x = JsonDynamic
| evaluate bag_unpack(x)
| mv-expand world
// With mv-expand we can extract the Group the user was added to.
// The easiest is to extract it from ActivityObjects column
CloudAppEvents
| where ActionType == "AddedToGroup"
| mv-expand ActivityObjects
| where ActivityObjects['Type'] == ('Group')
| project Timestamp, Application, IPAddress, Actor = AccountDisplayName, AddedUser = RawEventData.TargetUserOrGroupName, Group = ActivityObjects.Name
////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////
// startofday() function
// ref: https://docs.microsoft.com/en-us/azure/data-explorer/kusto/query/startofdayfunction
// Returns the time at the start of a day, with an optional time offset
//////////////////////////////
// KQL time filters and functions are UTC
// Returns the start of the day containing the date, shifted by an offset of days, if provided.
print startofday(datetime(2021-01-01 10:10:17))
IdentityInfo | where AccountUpn == 'meganb@seccxp.ninja'
let timeToSearch = startofday(datetime('2021-05-04'));
AADSignInEventsBeta
| where AccountObjectId == 'eababd92-9dc7-40e3-9359-6c106522db19' and Timestamp >= timeToSearch
| distinct Application, ResourceDisplayName, Country, City, IPAddress, DeviceName, DeviceTrustType, OSPlatform, IsManaged, IsCompliant, AuthenticationRequirement, RiskState, UserAgent, ClientAppUsed
// Step 1: understand the performed actions
let accountId = 'eababd92-9dc7-40e3-9359-6c106522db19';
let locations = pack_array('SG', 'DE', 'IE', 'AL', 'UK');
let timeToSearch = startofday(datetime('2021-05-04'));
CloudAppEvents
| where AccountObjectId == accountId and CountryCode in (locations) and Timestamp >= timeToSearch
| summarize by ActionType, CountryCode, AccountObjectId
| sort by ActionType asc
// Step 2: review the accessed emails
let accountId = 'eababd92-9dc7-40e3-9359-6c106522db19';
let locations = pack_array('SG', 'DE', 'IE', 'AL', 'UK');
let timeToSearch = startofday(datetime('2021-05-04'));
CloudAppEvents
| where ActionType == 'MailItemsAccessed' and CountryCode in (locations) and AccountObjectId == accountId and Timestamp >= timeToSearch
| mv-expand todynamic(RawEventData.Folders)
| extend Path = todynamic(RawEventData_Folders.Path), SessionId = tostring(RawEventData.SessionId)
| mv-expand todynamic(RawEventData_Folders.FolderItems)
| project SessionId, Timestamp, AccountObjectId, DeviceType, CountryCode, City, IPAddress, UserAgent, Path, Message = tostring(RawEventData_Folders_FolderItems.InternetMessageId)
| join kind=leftouter (
EmailEvents
| where RecipientObjectId == accountId
| project Subject, RecipientEmailAddress, SenderMailFromAddress, DeliveryLocation, ThreatTypes, AttachmentCount, UrlCount, InternetMessageId
)
on $left.Message == $right.InternetMessageId
| sort by Timestamp desc
// BONUS: get message details using Graph:
// https://graph.microsoft.com/v1.0/users/meganb@seccxp.ninja/messages?filter=internetMessageId eq '<b4acafd9-d086-4de0-8deb-c83118dae907@az.centralus.production.microsoft.com>'&select=subject,from,hasAttachments
// Step 3: review the accessed FolderItems
let accountId = 'eababd92-9dc7-40e3-9359-6c106522db19';
let locations = pack_array('SG', 'DE', 'IE', 'AL', 'UK');
let timeToSearch = startofday(datetime('2021-05-04'));
CloudAppEvents
| where ActionType == 'FilePreviewed' or ActionType == 'FileDownloaded' and CountryCode in (locations) and AccountObjectId == accountId and Timestamp >= timeToSearch
| project Timestamp, CountryCode, IPAddress, ISP, UserAgent, Application, ActivityObjects, AccountObjectId
| mv-expand ActivityObjects
| where ActivityObjects['Type'] in ('File', 'Folder') and ActivityObjects['Role'] == 'Target object'
| evaluate bag_unpack(ActivityObjects)
// Step 4: review deleted emails
let accountId = 'eababd92-9dc7-40e3-9359-6c106522db19';
let locations = pack_array('SG', 'DE', 'IE', 'AL', 'UK');
let timeToSearch = startofday(datetime('2021-05-04'));
CloudAppEvents
| where ActionType in~ ('MoveToDeletedItems', 'SoftDelete', 'HardDelete') and CountryCode in (locations) and AccountObjectId == accountId and Timestamp >= timeToSearch
| mv-expand ActivityObjects
| where ActivityObjects['Type'] in ('Email', 'Folder')
| evaluate bag_unpack(ActivityObjects)
| distinct Timestamp, AccountObjectId, ActionType, CountryCode, IPAddress, Type, Name, Id
| sort by Timestamp desc
// Step 5: review the created inbox rules
let accountId = 'eababd92-9dc7-40e3-9359-6c106522db19';
let locations = pack_array('SG', 'DE', 'IE', 'AL', 'UK');
let timeToSearch = startofday(datetime('2021-05-04'));
CloudAppEvents
| where ActionType contains_cs 'InboxRule' and CountryCode in (locations)
| extend RuleParameters = RawEventData.Parameters
| project Timestamp, CountryCode, IPAddress, ISP, ActionType, ObjectName, RuleParameters
| sort by Timestamp desc
// Step 6: identify potential other victims
let accountId = 'eababd92-9dc7-40e3-9359-6c106522db19';
let locations = pack_array('SG', 'DE', 'IE', 'AL', 'UK');
let timeToSearch = startofday(datetime('2021-05-04'));
let ips = (
CloudAppEvents
| where CountryCode in (locations)
| distinct IPAddress, AccountObjectId
);
ips
| join (CloudAppEvents | project ActivityIP = IPAddress, UserId = AccountObjectId) on $left.IPAddress == $right.ActivityIP
| distinct UserId
| join IdentityInfo on $left.UserId == $right.AccountObjectId
| distinct AccountDisplayName, AccountUpn, Department, Country, City, AccountObjectId
// Bonus: identify details sent by the malicious actorIdentityInfo
let accountId = 'eababd92-9dc7-40e3-9359-6c106522db19';
let timeToSearch = startofday(datetime('2021-05-04'));
CloudAppEvents
| where ActionType =~ 'send' and AccountObjectId == accountId // apply the right filter
| extend rawData = todynamic(RawEventData)
| extend UserKey = rawData.UserKey, MessageId = tostring(rawData.Item.InternetMessageId), Subject = rawData.Item.Subject, Attachments = rawData.Item.Attachments
| join (
EmailEvents
)
on $left.MessageId == $right.InternetMessageId
| sort by Timestamp desc
| project UserKey, Timestamp, AccountObjectId, AccountDisplayName, DeviceType, CountryCode, City, ISP, IPAddress, SenderIPv4, SenderIPv6, UserAgent, Subject, InternetMessageId, Attachments, RecipientEmailAddress, SenderMailFromAddress, DeliveryLocation, ThreatTypes, ConfidenceLevel
, AttachmentCount, UrlCount, MessageId