Blog Post

Microsoft Sentinel Blog
4 MIN READ

Azure Sentinel correlation rules: Active Lists out; make_list() in, the AAD/AWS correlation example

Ofer_Shezaf's avatar
Ofer_Shezaf
Icon for Microsoft rankMicrosoft
Nov 25, 2019

 

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:

 

What are Active Lists, and what they used for?

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:

  • To implement "complex" multi rule use cases, in which one rule updates the list, and another one inspects it.
  • For lookup tables, uploading reference information and looking it up as part of a rule.

In this blog post, I will focus on the former. Read 'Implementing Lookups in Azure Sentinel" to learn about the latter.

 

The "Failed AzureAD logons but success logon to AWS Console" rule

 

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:

  • An IP address from which there were many failed logins to AAD, presumably a brute force attempt.
  • A successful login to AWS console from the same IP, implying a potential breach.

 

Traditional Active List implementation

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.

  • The first identifies failed AAD logins and updates the count of failed logins for an IP in an Active List.
  • The second will identifies a successful AWS console login and check if the IP address appears in the Active List and the count is above a threshold.

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. 

 

Azure Sentinel make_list()

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?

  • The initial "where" closes filter out the relevant failure events. This would be similar to an ArcSight filter. Note that the time frame for the failed events is conveniently included in this clause using TimeGenerated >= ago(1d).
  • The first "summarize" statement counts the number of failed logins for each IP address, and the following "where" clause selects only those above a threshold. Note that the threshold can be dynamic, implementing behavioral analytics using the technique described here. A fixed threshold is often just as good and certainly easier for learning. 
  • The second "summarize" creates the list. Using two summarize statements is an optimization that shaves execution time as the second "summarize" is more costly but will be used only on above threshold IP addresses. 

Pretty straight forward, isn't it? We just created a list!

 

Wrapping up the rule

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.

Updated Dec 29, 2020
Version 12.0
  • I switched from make_list, which I normally use, to makelist to adjust to the github query, but was not ware of the difference. I will discuss with our research team if we need to modify the original.

  • MiteshAgrawal's avatar
    MiteshAgrawal
    Brass Contributor

    Hi Ofer_Shezaf ,

     

    Thanks for a very good post.

     

    I am also interested in "A future post will discuss implementing lookup tables in Azure Sentinel.". I am very new to Azure Sentinel and want to integrate custom threat intelligence from our company's website.

     

    If I download the TI feeds from our website and paste it somewhere on my local machine, then how can I update those feeds in Active lists (or similar in Sentinel) and call them against rules.

     

    Thanks in Advance.


    Regards,

    Mitesh Agrawal

  • MiteshAgrawal's avatar
    MiteshAgrawal
    Brass Contributor

    Thanks Ofer_Shezaf . The post is very useful. My requirement can be met with the link you provided. 

     

    One more query I have is whether can I read data from a file in my local machine if I do not want to create a BLOB object?

     

    Regards,

    Mitesh Agrawal

  • abhishekmahadik's avatar
    abhishekmahadik
    Copper Contributor

    Ofer_Shezaf we are using the summarize function in the one of use case.  We are able to generate an alert but it is not capturing the base events in the alerts. Please refer to below query.

     

    Syslog
    | where SyslogMessage contains "invalid user" and isnotempty(User_Name_CF)
    | summarize TotalLogonAttempts = count() by HostIP
    | where TotalLogonAttempts >= 10

     

    is there any way to capture the base events?