SIEM 103 — Detect Windows bruteforce part 2

This post is a follow up of the post SIEM 102 — Detect Windows bruteforce where I explained how to create a detection Use Case to detect a Windows bruteforce.

In this post I will explain how we can enhance the original detection logic by having a lower False Positive rate.

As I explained in the last section of the initial post, it is important to manage False Positives (FP). In the past few months, I spent some time to look for ways to reduce FP and this post will summarize them.

The context

We sometime received some alerts that we needed to investigate to understand what was happening. After the investigation, we found that it was sometime because someone mistyped their username, or it was a on a disabled account (through an automated script for example), etc. All these cases where not a bruteforce attack currently occurring on a user account.

We wanted to lower the amount of alerts we receive and only receive useful alerts.

We used 2 tactics to do so:

  1. Have different Threshold based on the logon type
  2. Adjust the query to only alert on relevant reasons

Threshold based on the logon type

If you look at what information an event for a bad logon has (event id 4625: you’ll notice it has a Logon Type. You can find the detail of what these Logon Type code means on the page

2 Interactive (logon at keyboard and screen of system)

3 Network (i.e. connection to shared folder on this computer from elsewhere on network)

4 Batch (i.e. scheduled task)

5 Service (Service startup)

7 Unlock (i.e. unnattended workstation with password protected screen saver)

8 NetworkCleartext (Logon with credentials sent in the clear text. Most often indicates a logon to IIS with “basic authentication”) See this article for more information.

9 NewCredentials such as with RunAs or mapping a network drive with alternate credentials. This logon type does not seem to show up in any events. If you want to track users attempting to logon with alternate credentials see 4648. MS says “A caller cloned its current token and specified new credentials for outbound connections. The new logon session has the same local identity, but uses different credentials for other network connections.”

10 RemoteInteractive (Terminal Services, Remote Desktop or Remote Assistance)

11 CachedInteractive (logon with cached domain credentials such as when logging on to a laptop when away from the network)

A description for all the Logon Types. Source:

Based on this, we created 2 different alerts with 2 different threshold:

  1. If the connection is from the network and non-interactive (if the Logon Type is 3), we alert if there was 25 tentative for the same user from the same source. This greatly reduces the FP for cases like Windows trying to list shares (which happens a lot). We also reduced the severity for this case.
  2. If the connection is of any other type (if the Logon Type is not 3), we alert if there was 5 tentative for the same user from the same source. The rule is “if the Logon Type is not 3” but in reality it ends up being often of either a type 2 or a type 10 meaning they are interactive. These kind of alerts do not occur often and are really reliable (even more so if we count in the tactic from the next section). We kept the original severity for this one.

Having these 2 different alerts is really useful to receive relevant alerts and knowing in one look if it’s interactive or not (we added a group by on the Logon Type so we have this information in the email we receive).

Alert only on relevant reasons

If you look at what information an event for a bad logon has (event id 4625: you’ll notice it has a Sub Status. This Sub Status gives a reason for the failed logon. You’ll notice that some of these reasons are not relevant to detect a bruteforce (for example, if the event is created for a user that does not exists, this is not a bruteforce (of course, we could create an alert to detect this specific Use Case)). However, this table is incomplete. The complete list can be found on Microsoft’s website: Here is the table:

Status\Sub-Status Code Description

0XC000005E There are currently no logon servers available to service the logon request.

0xC0000064 User logon with misspelled or bad user account

0xC000006A User logon with misspelled or bad password

0XC000006D The cause is either a bad username or authentication information

0XC000006E Indicates a referenced user name and authentication information are valid, but some user account restriction has prevented successful authentication (such as time-of-day restrictions).

0xC000006F User logon outside authorized hours

0xC0000070 User logon from unauthorized workstation

0xC0000071 User logon with expired password

0xC0000072 User logon to account disabled by administrator

0XC00000DC Indicates the Sam Server was in the wrong state to perform the desired operation.

0XC0000133 Clocks between DC and other computer too far out of sync

0XC000015B The user has not been granted the requested logon type (also called the logon right) at this machine

0XC000018C The logon request failed because the trust relationship between the primary domain and the trusted domain failed.

0XC0000192 An attempt was made to logon, but the Netlogon service was not started.

0xC0000193 User logon with expired account

0XC0000224 User is required to change password at next logon

0XC0000225 Evidently a bug in Windows and not a risk

0xC0000234 User logon with account locked

0XC00002EE Failure Reason: An Error occurred during Logon

0XC0000413 Logon Failure: The machine you are logging on to is protected by an authentication firewall. The specified account is not allowed to authenticate to the machine.

A description for all the Sub Status. Source:

So after analyzing all the Sub Statuses, we ended up removing all the ones we don’t care about in the context of a bruteforce detection. We ended up adding to the query: (…) and if the Sub Status is not one of:

("0XC000005E", "0XC000006E", "0xC000006F", "0xC0000070", "0xC0000071", "0xC0000072", "0XC00000DC", "0XC0000133", "0XC000015B", "0XC0000192", "0XC0000225", "0XC00002EE", "0x0", "0X80090325", "0xC0000064")

By removing all of these, we removed almost all FP and only detect true Windows bruteforce. For example, we received a total of 9 alerts in the last 90 days (at the time of writing these lines) and after verification, they were all cases where the person made an error (new employee, recently changed the password, etc.).


As you can see, any alert can be tweaked to have a really low level of False Positives while also staying relevant. We can use many tactics and only detect a true Windows bruteforce. The trick is to invest some time to analyze what information we have and think what tactics can be used to keep only relevant alerts.

Feel free to leave your comment down here for any questions or comments.

Originally published at on June 4, 2022.



Passionate about information security, development and technology in general, I like to share my experience with different technologies. I also love travel!

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Tristan Dostaler

Tristan Dostaler

Passionate about information security, development and technology in general, I like to share my experience with different technologies. I also love travel!