Skip to content

Commit 95db054

Browse files
authored
Merge pull request microsoft#339 from microsoft/mjmelone-patch-62
Create Hunting in MCAS.csl
2 parents 7ef3809 + d5a9d09 commit 95db054

File tree

1 file changed

+225
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)