|
| 1 | +print Series = 'Tracking the Adversary with MTP Advanced Hunting', EpisodeNumber = 1, Topic = 'KQL Fundamentals', Presenter = 'Michael Melone, Tali Ash', Company = 'Microsoft' |
| 2 | + |
| 3 | +// Language Reference: https://docs.microsoft.com/en-us/azure/kusto/query/ |
| 4 | +// Advanced Hunting Reference: https://docs.microsoft.com/en-us/microsoft-365/security/mtp/advanced-hunting-schema-tables?view=o365-worldwide |
| 5 | + |
| 6 | +// --------------- |
| 7 | + |
| 8 | +// What is KQL \ Azure Data Explorer? |
| 9 | +// - Write Once, Read Many (WORM) dataset |
| 10 | +// - Used in a variety of Microsoft products including |
| 11 | +// + Defender ATP Advanced Hunting |
| 12 | +// + Microsoft Threat Protection Advanced Hunting |
| 13 | +// + Azure Sentinel |
| 14 | +// + Azure Data Explorer |
| 15 | +// - Tuned to work best with log data |
| 16 | +// - Case sensitive |
| 17 | +// - Automatically expires records based on a specified interval (up to 10 years) |
| 18 | + |
| 19 | +// --------------- |
| 20 | + |
| 21 | +// When using Kusto datasources |
| 22 | +// - If the data source is log-based, try to reduce the timeframe |
| 23 | +// - More current data is likely to be in hot storage and will return more quickly |
| 24 | +// - Try to reduce data earlier in the query before joining or manipulating it |
| 25 | + |
| 26 | +// --------------- |
| 27 | + |
| 28 | +// Getting Started: Query Format |
| 29 | +// |
| 30 | +// DataSource |
| 31 | +// | filters \ modifiers \ limiters |
| 32 | + |
| 33 | +DeviceProcessEvents |
| 34 | +| take 100 |
| 35 | + |
| 36 | +// DeviceProcessEvents |
| 37 | +// ref: https://docs.microsoft.com/en-us/microsoft-365/security/mtp/advanced-hunting-deviceprocessevents-table?view=o365-worldwide |
| 38 | +// Process creation and related events |
| 39 | +// - The newly-launched process |
| 40 | +// - The process which initiated the process |
| 41 | +// + The device the process |
| 42 | +// + The identity the process was launched as |
| 43 | + |
| 44 | +// take |
| 45 | +// Returns rows up to a pre-set count. Good for testing out your query at a small scale before use. |
| 46 | + |
| 47 | +// Note: There is no order or consistency when using take without sorting! |
| 48 | + |
| 49 | +// SQL Equivalent: SELECT TOP 15 * FROM DeviceProcessEvents |
| 50 | + |
| 51 | +// --------------- |
| 52 | + |
| 53 | +// Data sources can be tables, functions, variables |
| 54 | + |
| 55 | +let foo = "bar"; |
| 56 | +print foo |
| 57 | + |
| 58 | +// let |
| 59 | +// Declares a variable which can be used later in the query. |
| 60 | +// - Values can be: |
| 61 | +// + Scalar (single value) |
| 62 | +// + Tabular (a 2-dimensional table) |
| 63 | +// + A function |
| 64 | +// + Dynamic (a JSON-formatted object that can be addressed using dotted notation (this.that)) |
| 65 | +// - A semicolon must exist after every let statement! |
| 66 | + |
| 67 | +// print |
| 68 | +// Outputs a scalar value |
| 69 | + |
| 70 | +// --------------- |
| 71 | + |
| 72 | +DeviceLogonEvents |
| 73 | +| count |
| 74 | + |
| 75 | +// DeviceLogonEvents |
| 76 | +// A table containing a row for each logon a device enrolled in Defender ATP |
| 77 | +// Contains |
| 78 | +// - Account information associated with the logon |
| 79 | +// - The device which the account logged onto |
| 80 | +// - The process which performed the logon |
| 81 | +// - Network information (for network logons) |
| 82 | +// - Timestamp |
| 83 | + |
| 84 | +// count |
| 85 | +// Returns the row count for a tablular dataset |
| 86 | + |
| 87 | +// --------------- |
| 88 | + |
| 89 | +AppFileEvents |
| 90 | +| take 100 |
| 91 | +| sort by Timestamp desc |
| 92 | + |
| 93 | + |
| 94 | +// AppFileEvents |
| 95 | +// ref: https://docs.microsoft.com/en-us/microsoft-365/security/mtp/advanced-hunting-appfileevents-table?view=o365-worldwide |
| 96 | +// Information regarding activity relating to files stored in cloud services |
| 97 | +// monitored by Microsoft Cloud App Security (MCAS), including |
| 98 | +// - The cloud application name |
| 99 | +// - The type of action performed |
| 100 | +// - The item the action was performed on |
| 101 | +// - The identity which performed the action |
| 102 | +// - The IP address and geolocation |
| 103 | + |
| 104 | +// sort |
| 105 | +// Orders the dataset based on the specified column |
| 106 | + |
| 107 | +// SQL Equivalent: SELECT TOP 100 * FROM DeviceFileEvents ORDER BY Timestamp desc |
| 108 | + |
| 109 | +// --------------- |
| 110 | + |
| 111 | +DeviceRegistryEvents |
| 112 | +| top 100 by Timestamp desc |
| 113 | + |
| 114 | +// DeviceRegistryEvents |
| 115 | +// Registry changes which occurred on a Windows device monitored by Defender ATP |
| 116 | +// Contains |
| 117 | +// - Registry information (Key, Value, Data) |
| 118 | +// - Device information |
| 119 | +// - The process which performed the operation |
| 120 | +// - Timestamp |
| 121 | + |
| 122 | +// top |
| 123 | +// Returns an ordered list of rows based on the column specified |
| 124 | + |
| 125 | +// SQL Equivalent: SELECT TOP 100 * FROM DeviceRegistryEvents ORDER BY Timestamp desc |
| 126 | + |
| 127 | +// --------------- |
| 128 | + |
| 129 | +DeviceNetworkEvents |
| 130 | +| take 1000 |
| 131 | +| distinct RemoteIP, RemoteUrl |
| 132 | + |
| 133 | +// DeviceNetworkEvents |
| 134 | +// Table containing inbound and outbound network connections and attempts from a device monitored by Defender ATP |
| 135 | +// Contains |
| 136 | +// - Networking information (source and destination IP and port, URL, protocol) |
| 137 | +// - Device information |
| 138 | +// - The process which made or received the connection |
| 139 | +// - Timestamp |
| 140 | + |
| 141 | +// distinct |
| 142 | +// Returns a table of unique results based on the column(s) specified |
| 143 | + |
| 144 | +// SQL Equivalent: SELECT DISTINCT RemoteIP, RemoteUrl FROM DeviceNetworkEvents |
| 145 | + |
| 146 | +// --------------- |
| 147 | + |
| 148 | +DeviceInfo |
| 149 | +| take 100 |
| 150 | +| project DeviceId, DeviceName, OSPlatform |
| 151 | + |
| 152 | +// DeviceInfo |
| 153 | +// Operating information about a device monitored by Defender ATP |
| 154 | +// Contains |
| 155 | +// - Device name, ID |
| 156 | +// - Operating system information |
| 157 | +// - Public IP address |
| 158 | +// - Logged on user |
| 159 | +// - Machine group |
| 160 | + |
| 161 | +// project |
| 162 | +// Can be used to |
| 163 | +// - Reduce columns returned from a dataset |
| 164 | +// - Rename columns in a dataset |
| 165 | +// - Create calculated columns |
| 166 | + |
| 167 | +// DataSource |
| 168 | +// | project Column1, Column2, Column3 = Column1 + Column2 |
| 169 | + |
| 170 | +// SQL Equivalent: The column list in a query statement |
| 171 | +// SELECT [this is the project statement] FROM DataSource |
| 172 | + |
| 173 | +DeviceInfo |
| 174 | +| project Timestamp, DeviceName, Four = 2 + 2 |
| 175 | +| take 100 |
| 176 | + |
| 177 | +// -------------- |
| 178 | + |
| 179 | +DeviceNetworkInfo |
| 180 | +| take 100 |
| 181 | +| project-away Timestamp |
| 182 | + |
| 183 | +// DeviceNetworkInfo |
| 184 | +// Local network configurations for a device monitored by Defender ATP |
| 185 | + |
| 186 | +// Other useful project commands: |
| 187 | + |
| 188 | +// project-away |
| 189 | +// Removes columns from the dataset |
| 190 | + |
| 191 | +// project-rename |
| 192 | +// Renames a column |
| 193 | + |
| 194 | +// project-reorder |
| 195 | +// Changes the order of columns in the results making the specified columns first |
| 196 | +// No real change to the data, just how its represented |
| 197 | + |
| 198 | +// --------------- |
| 199 | + |
| 200 | +DeviceImageLoadEvents |
| 201 | +| take 100 |
| 202 | +| extend DomainAndUser = strcat(InitiatingProcessAccountDomain, '\\', InitiatingProcessAccountName) |
| 203 | +| project-reorder DomainAndUser, InitiatingProcessAccountDomain, InitiatingProcessAccountName |
| 204 | + |
| 205 | +// DeviceImageLoadEvents |
| 206 | +// Identifies any DLLs loaded by a process. Useful for tracking DLL sideloading attacks. |
| 207 | +// Contains |
| 208 | +// - The process that loaded the library |
| 209 | +// - The module loaded by the process |
| 210 | +// - The device where the load occurred |
| 211 | +// - Timestamp |
| 212 | + |
| 213 | +// extend |
| 214 | +// Adds a column to the current dataset |
| 215 | + |
| 216 | +// strcat() |
| 217 | +// Concatenates two or more strings |
| 218 | + |
| 219 | +// --------------- |
| 220 | + |
| 221 | +AppFileEvents |
| 222 | +| where Timestamp > ago(3d) |
| 223 | + |
| 224 | +// where |
| 225 | +// Used to filter a tables results based on a Boolean expression |
| 226 | + |
| 227 | +// DataSource |
| 228 | +// | where Column == "value" |
| 229 | + |
| 230 | +// SQL Equivalent |
| 231 | +// SELECT * FROM SecurityEvent WHERE EventID = 4624 |
| 232 | + |
| 233 | +// ago() |
| 234 | +// Function used to identify a timespan relative to the current date and time |
| 235 | +// Used with one of the following quantifiers: |
| 236 | +// d: days |
| 237 | +// h: hours |
| 238 | +// m: minutes |
| 239 | +// s: seconds |
| 240 | +// ms: milliseconds |
| 241 | +// microsecond: microseconds |
| 242 | +// tick: ticks (100 nanosecond intervals) |
| 243 | + |
| 244 | +// Important note: The most effective way to improve query performance in KQL |
| 245 | +// is filtering based on time. |
| 246 | + |
| 247 | +// ---------------------------------- |
| 248 | + |
| 249 | +// Note that Kusto is a case sensitive language and |
| 250 | +// many of the operators are case sensitive. |
| 251 | + |
| 252 | +print IsItEqual = 'TEST' == 'test' |
| 253 | + |
| 254 | +// For a case insensitive string search, use =~ |
| 255 | + |
| 256 | +print IsItEqual = 'TEST' =~ 'test' |
| 257 | + |
| 258 | +// Common Operators and their case insensitive counterparts |
| 259 | +// __________________________________________________________________ |
| 260 | +// | Case Sensitive | Case Insensitive | Operation | |
| 261 | +// -------------------------------------------------------------------- |
| 262 | +// | == | =~ | Equality | |
| 263 | +// | != | !~ | Inequality | |
| 264 | +// | has_cs | has | Term comparison (whole word) | |
| 265 | +// | !has_cs | !has | Term comparison (whole word) | |
| 266 | +// | hasprefix_cs | hasprefix | Term prefix comparison (any) | |
| 267 | +// | !hasprefix_cs | !hasprefix | Term prefix comparison (any) | |
| 268 | +// | hassuffix_cs | hassuffix | Term suffix comparison (any) | |
| 269 | +// | !hassuffix_cs | !hassuffix | Term suffix comaprison (any) | |
| 270 | +// | contains_cs | contains | Substring | |
| 271 | +// | !contains_cs | !contains | Substring | |
| 272 | +// | startswith_cs | startswith | String prefix | |
| 273 | +// | !startswith_cs | !startswith | String prefix | |
| 274 | +// | endswith_cs | endswith | String suffix | |
| 275 | +// | !endswith_cs | !endswith | String suffix | |
| 276 | +// | in | in~ | Array element match | |
| 277 | +// | !in | !in~ | Array element match | |
| 278 | +// | | has_any | Term array match | |
| 279 | +// | matches regex | | Regular expression match | |
| 280 | +// -------------------------------------------------------------------- |
| 281 | + |
| 282 | +print IsItEqual = "quick" in ("The", "Quick", "Brown", "Fox") |
| 283 | + |
| 284 | +print IsItEqual = pack_array("lorem","ipsum","dolor") has "Dolor" |
| 285 | + |
| 286 | +print IsItEqual = "Microsoft" contains_cs "ICR" |
| 287 | + |
| 288 | +// For a list of all string operators: https://docs.microsoft.com/en-us/azure/kusto/query/datatypes-string-operators |
| 289 | + |
| 290 | +// --------------- |
| 291 | + |
| 292 | +// Special characters \ escaping |
| 293 | + |
| 294 | +// In KQL, the '\' character is the escape character. If you want to use a '\' |
| 295 | +// in your query you will need to either escape it by using '\\', or you can |
| 296 | +// make it a string literal by prepending '@' before the string |
| 297 | + |
| 298 | +print '\\ This \\ example \\ uses \\ the \\ escape \\ method \\' |
| 299 | + |
| 300 | +// Now using the string literal method |
| 301 | +print @'\ This \ example \ uses \ the \ string \ literal \ method \' |
| 302 | + |
| 303 | +// --------------- |
| 304 | + |
| 305 | +// Checking for null or blank values |
| 306 | + |
| 307 | +// isnull(Column) / isnotnull(Column) |
| 308 | +// - Checks for null values |
| 309 | + |
| 310 | +// SQL Equivalent: SELECT TimeGenerated, EventData FROM SecurityEvent WHERE EventData IS NOT NULL |
| 311 | + |
| 312 | +print isnull("") |
| 313 | + |
| 314 | +// isempty(Column) / isnotempty(Column) |
| 315 | +// - Checks for null values or empty strings |
| 316 | + |
| 317 | +// SQL Equivalent: SELECT TimeGenerated, EventData FROM SecurityEvent WHERE EventData LIKE '%' |
| 318 | + |
| 319 | +IdentityQueryEvents |
| 320 | +| where isnotempty(AccountSid) |
| 321 | +| take 100 |
| 322 | + |
| 323 | +// IdentityQueryEvents |
| 324 | +// - contains query activities performed against Active Directory objects, such as users, groups, devices, and domains monitored by Azure ATP |
| 325 | +// - Includes SAMR, DNS and LDAP requests |
| 326 | + |
| 327 | +// --------------- |
| 328 | + |
| 329 | +search 'microsoft.com' |
| 330 | +| take 10 |
| 331 | +| project-reorder RemoteUrl |
| 332 | + |
| 333 | +// search |
| 334 | +// Searches the entire dataset for a given value |
| 335 | +// Can be used to search the entire database (all tables and columns) all at once. |
| 336 | +// Columns will be an aggregate of every table that brought back 1+ results, with |
| 337 | +// columns having the same name merged together |
| 338 | + |
| 339 | +// No true SQL equivalent (aside from indexing every table and searching the index, or unioning every table and column and searching that... yuck) |
| 340 | + |
| 341 | +IdentityInfo |
| 342 | +| search "administrator" |
| 343 | +| take 100 |
| 344 | +| project-reorder AccountUpn, AccountName, AccountDisplayName, Surname, EmailAddress, JobTitle |
| 345 | + |
| 346 | +// IdentityInfo |
| 347 | +// - Contains information about users in Azure Active Directory |
| 348 | + |
| 349 | +// Can be used with string equality comparisons. Comparison is row-based |
| 350 | + |
| 351 | +search "administrator" and "cmd" |
| 352 | +| take 100 |
| 353 | +| project-reorder ProcessCommandLine, FileName, AccountName, FolderPath, AccountDisplayName, Surname, EmailAddress, JobTitle, AccountUpn |
0 commit comments