Writing alert rules using KQL is powerful but does not have to be complex. A good example would be rules which in traditional SIEM use Active Lists (or Reference Sets, depending on your SIEM). When presenting KQL rules, I am often asked how to implement Active Lists in Sentinel. While replicating Active Lists in Sentinel is a good topic for another blog post, I will focus here on avoiding Active Lists in the first place using Sentinel query-based rules. You just don't need them in most cases.
This post of part of a series of blog posts on writing rules in Azure Sentinel:
Active Lists are the ArcSight term for, well, tables. Unlike the main event repository tables, which are immutable in a SIEM, Active Lists can be modified, for example, by rules.
Active Lists are mostly used for two purposes:
In this blog post, I will focus on the former. Read 'Implementing Lookups in Azure Sentinel" to learn about the latter.
To avoid a theoretical discussion, let's focus on a specific example. I picked this rule, which @petebrayne from our research team wrote as it is a powerful use case yet simple enough for demonstration purposes. The rule is also built-in into Azure Sentinel.
The rule looks for:
In a traditional SIEM, this use case would usually require two rules and an Active list (see here an ArcSight implementation of a similar Brute Force rule or the official ArcSight guide.
This approach works, but it is far from trivial and is hard to maintain. It requires two unrelated rules and a separate Active List object. The actual time frames for the use case are actually the Active List record TTL rather than any rule property. Make the scenarios slightly more complex - by adding a 3rd event condition - and it becomes unmanageable.
Let's look at Pete's rule. The first part takes full advantage of the fact that query-based rules can look back to create the list on demand without requiring a second rule and an intermediate object:
let signin_threshold = 5;
let suspicious_signins =
SigninLogs
| where TimeGenerated >= ago(1d)
| where ResultType !in ("0", "50125", "50140")
| where IPAddress != "127.0.0.1"
| summarize count() by IPAddress
| where count_ > signin_threshold
| summarize make_list(IPAddress);
What is this section doing?
Pretty straight forward, isn't it? We just created a list!
The second part of the rule only has to identify AWS login failures (Blue) and then check if the IP address is in the list from step 1 (Magenta). The rest (Green) is only used to enhance the information for the analyst use.
AWSCloudTrail
| where TimeGenerated > ago(1d)
| where EventName == "ConsoleLogin"
| extend LoginResult = tostring(parse_json(ResponseElements).ConsoleLogin)
| where LoginResult == "Success"
| where SourceIpAddress in (suspicious_signins)
| extend Reason = "Multiple failed AAD logins from IP address"
| extend MFAUsed = tostring(parse_json(AdditionalEventData).MFAUsed)
| extend User = iif(UserIdentityUserName == "", UserIdentityType, UserIdentityUserName)
| project TimeGenerated, Reason, LoginResult, EventTypeName, UserIdentityType, User, AWSRegion, SourceIpAddress, UserAgent, MFAUsed
| extend timestamp = TimeGenerated, AccountCustomEntity = User, IPCustomEntity = SourceIpAddress
Simple. Isn't it? Since the Azure Sentinel rule does not depend on a state machine, it is easier to build, test, and maintain.
Next time we will discuss the other use of Active Lists: lookups.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.