When using Azure Sentinel, you are bound to get some false positives. No detection rule is perfect. In this blog post, we will learn how to handle false positives in scheduled analytics rules.
In most cases, false positives stem from specific entities such as users or IP addresses which should be excluded. Common scenarios are:
The solution is to modify the rule queries, either by including the exceptions directly in the rule, or preferably, when possible, including a reference to a watchlist and managing the exception list in the watchlist.
Taking the typical rule preamble, you can add the blue line at the beginning of the query:
let timeFrame = 1d;
let logonDiff = 10m;
SigninLogs
| where TimeGenerated >= ago(timeFrame)
| where IPAddress in ('10.0.0.8', '192.168.12.1')
…
The relevant exception is not limited to IP addresses and might be for specific users (using the UserPrincipalName field) or Apps (using the AppDisplayName).
You can also exclude multiple attributes:
| where IPAddress in ('10.0.0.8', '192.168.12.1')
| where UserPrincipalName == 'user@microsoft.com'
Or, to implement a more fine-grained exception when applicable, to reduce the chance for false negatives, by combining attributes:
| where IPAddress == '10.0.0.8' and UserPrincipalName == 'user@microsoft.com'
The third use case described above, excluding IP ranges used by the organization, requires subnet exclusion. The following examples show how to exclude subnets. Note that since the ipv_lookup operator is an enrichment operator and not a filtering operator, the filtering is actually done in the following line by inspecting those events for which a match was not made.
let subnets = datatable(network:string) [ "111.68.128.0/17", "5.8.0.0/19", ...];
let timeFrame = 1d;
let logonDiff = 10m;
SigninLogs
| where TimeGenerated >= ago(timeFrame)
| evaluate ipv4_lookup(subnets, IPAddress, network, return_unmatched = true)
| where isempty(network)
| where ResultType == "0"
…
You can use a Watchlist to manage the list of IP addresses outside of the rule itself. When applicable, this is the preferred solutions and has several advantages:
Using a watchlist is rather similar to using a direct exception:
let timeFrame = 1d;
let logonDiff = 10m;
let allowlist = (_GetWatchlist('ipallowlist') | project IPAddress);
SigninLogs
| where TimeGenerated >= ago(timeFrame)
| where IPAddress in (allowlist)
…
Subnets filtering can also be done using a watchlist by replacing in the subnets example above, the subnets table definition with a watchlist:
let subnets = _GetWatchlist('subnetallowlist');
I hope you found this useful!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.