28 December 2011

Defeat Domain User Spraying & Brute Forcing of Passwords

A while back, LaNMaSteR53 (Tim Tomes) discussed a method for brute forcing domain default passwords while avoiding account lockout.  This discussion was in response to a video by Dave Hoelzer on using PowerShell to hack domain user accounts. 

The method proposed by LaNMaSteR53 relied on connecting to the IPC$ share of a domain controller.  While there is a discussion to be had regarding the use of administrative shares, we will table that for now and focus instead on defeating LaNMaSteR53 and his desire to control our network. 
Perhaps the most obvious way to detect this would be to review your Windows Security Event Logs on your Active Directory domain controllers.  But if LaNMaSteR53 is successful, he will eventually gain domain admin privileges and clean up his tracks.  Unless you are pumping your Windows Security Event Logs in real-time into a SIEM system of some sort and correlating based on failed logon attempts, chances are you may not see the activity at all or you may not detect it until it is too late.  So, how can you defeat or detect the presence of this activity without a snazzy, expensive SIEM system? 
One way would be via an Intrusion Detection System (IDS) such as the open source Snort product.  With a pretty simple rule, you can monitor responses sent from your Active Directory domain controllers to clients requesting authentication.  If the count of all failed authentication attempts from any given client in your enterprise exceeds a certain threshold in a specified period of time, you may be under attack even if you do not have a large number of disabled user accounts, etc. 
If an organization locks user accounts after, say, 5 failed logon attempts, one might choose to configure a rule to look for more than 4 failed logon attempts from a single client in a period of 2 minutes.  The following Snort rule accomplishes this purpose:
alert tcp any 88 -> any any (msg:"Possible domain user spraying detected"; \
flow:established, to_client; \
content:"|05|"; offset:14; depth:15; \
content:"|1e|"; distance:4; within:1; \
content:"|18|"; distance:30; within:1; \
detection_filter:track by_dst, count 4, seconds 120; \
reference:url,foxtrot7security.blogspot.com/2011/12/defeat-domain-user-spraying-brute_28.html; \
classtype:attempted-user; \
sid:1700000; \
rev:0;)

Like a lot of things in Security, you may end up with some false positives.  Some tuning of the “count” and “seconds” thresholds based on your local environment should cut out most of the noise while allowing you to detect truly malicious activity. 
Now for the truly curious who are asking the question, “So how does this rule work?”… 
Microsoft adopted Kerberos as the preferred authentication protocol for Windows 2000 and subsequent Active Directory domains.  While a comprehensive discussion of Kerberos 5 is beyond the scope of this post, there is a good Microsoft TechNet article that explains it pretty well.  Kerberos 5 is also defined in RFC 5120.
By default, Microsoft Active Directory has a Kerberos feature called pre-authentication enabled.  Pre-authentication makes offline password guessing attacks very difficult and, during the course of the user authentication, a Kerberos error is generated if an invalid user password is presented as part of the pre-authentication process.  The Kerberos error generated in this case is KDC_ERR_PREAUTH_FAILED.  This error code is set within the context of a KRB-ERROR structure is defined in the RFC as follows: 
   KRB-ERROR ::= [APPLICATION 30] SEQUENCE {
           pvno            [0] INTEGER (5),
           msg-type        [1] INTEGER (30),
           ctime           [2] KerberosTime OPTIONAL,
           cusec           [3] Microseconds OPTIONAL,
           stime           [4] KerberosTime,
           susec           [5] Microseconds,
           error-code      [6] Int32,
           crealm          [7] Realm OPTIONAL,
           cname           [8] PrincipalName OPTIONAL,
           realm           [9] Realm -- service realm --,
           sname           [10] PrincipalName -- service name --,
           e-text          [11] KerberosString OPTIONAL,
           e-data          [12] OCTET STRING OPTIONAL }

These responses are delivered from the KDC (aka the targeted domain controller) to the client via TCP port 88 which is the registered port for Kerberos.  All we need to do is inspect the packets returned to the client for the proper pvno (protocol version number), msg-type and error-code to be able to detect a failed login.  Then we simply count the number of failed logins versus our threshold values for count and seconds and—viola—we know what is going on in our network! 

So how does this work via the Snort rule presented?  First, we start with a packet capture of a failed login and look for the packet containing the KDC_ERR_PREAUTH_FAILED message.  The payload might look something like this: 

0000        00 00 00 e5 7e 81  e2 30 81 df a0 03 02 01   ......~. .0......
0010  05 a1 03 02 01 1e a4 11  18 0f 32 30 31 31 31 32   ........ ..201112
0020  32 31 32 31 31 30 35 39  5a a5 05 02 03 0e cb ab   21211059 Z.......
0030  a6 03 02 01 18 a9 06 1b  04 58 58 61 64 aa 19 30   ........ .XXad..0
0040  17 a0 03 02 01 02 a1 10  30 0e 1b 06 6b 72 62 74   ........ 0...krbt
0050  67 74 1b 04 58 58 61 64  ac 81 90 04 81 8d 30 81   gt..XXad ......0.
0060  8a 30 49 a1 03 02 01 0b  a2 42 04 40 30 3e 30 09   .0I..... .B.@0>0.
0070  a0 03 02 01 17 a1 02 04  00 30 0a a0 04 02 02 ff   ........ .0......
0080  7b a1 02 04 00 30 09 a0  03 02 01 80 a1 02 04 00   {....0.. ........
0090  30 1a a0 03 02 01 03 a1  13 04 11 58 58 41 44 2e   0....... ...XXAD.
00a0  58 58 2e 43 4f 4d 6f 6f  6f 66 75 73 30 3d a1 03   XX.COMdo ofus0=..
00b0  02 01 13 a2 36 04 34 30  32 30 05 a0 03 02 01 17   ....6.40 20......
00c0  30 06 a0 04 02 02 ff 7b  30 05 a0 03 02 01 80 30   0......{ 0......0
00d0  1a a0 03 02 01 03 a1 13  1b 11 58 58 41 44 2e 58   ........ ..XXAD.X
00e0  58 2e 43 4f 4d 64 6f 6f  66 75 73                  X.COMdoo fus   

One hint:  Wireshark understands Kerberos packets (and many other protocols too!) and makes this much easier and far more understandable.  Hence, using Wiresshark is highly recommended when doing protocol analysis.

Let’s look at our rule again and break down the important parts in the context of the packet.  We start with:
alert tcp any 88 -> any any (msg:"Possible domain user spraying detected"; \
We are looking at “any” IP sending traffic on tcp port 88 since we are inspecting Kerberos traffic.  The directional arrow specifies that we are looking for the traffic to originate on tcp port 88.  (More advanced topic:  you could improve rule performance by creating a variable in Snort equating it only to your domain controllers.  Your sensors would have less traffic to inspect that way!)

The next line says that the communication should be part of an established TCP session between the client and the server and that the response traffic should be destined for the client from the server:

flow:established, to_client; \

Now we start our content inspection.  Omitting the first 14 bytes of the packet (essentially Kerberos stuff we don’t care about, but which is defined in the RFC) we can find our protocol version number or pvno.  We expect this value to be hexadecimal “05” since we are inspecting Kerberos version 5 traffic. 

content:"|05|"; offset:14; depth:15; \

Moving deeper into the packet, we find the msg-type field and look for a decimal value of 30 as specified in the RFC snippet shown above.  Expressed in hexadecimal, this value is “1e”.  If we get this value, we know that we are now dealing with a Kerberos 5 error message. 

content:"|1e|"; distance:4; within:1; \

At this point, we need only make sure we have found the pre-authentication failure.  Buried deep in the packet 30 bytes beyond the msg-type field, we find the err-code field which should contain a decimal value of 24 if this is a pre-authentication failure.  This corresponds to a hexadecimal value of “18”. 

content:"|18|"; distance:30; within:1; \

And to track how many responses are going to a client, we use the following line: 

detection_filter:track by_dst, count 4, seconds 120; \

The track by_dst says to start a new set of counters for each client IP requesting authentication.  Count and seconds are our thresholds for alerting.  Remember, we will actually alert on the 5th failed authentication attempt if we specify a count of 4 because this is saying MORE than 4 detected events. 

I didn’t spell it out above, but there really is a method to the madness when you are creating these types of rules: 

1. Make sure you understand the problem you want to solve. 
2. Capture traffic that illustrates the problem.
3. Look at the traffic and understand it thoroughly based on the protocol definition. 
    Make sure you understand the traffic flow characteristics and directional nature of the traffic.
4. Identify anything in the traffic that would allow you to spot the problem you are trying to solve.
5. Write a rule using what you have learned. 
6.  Test and implement the rule once you know that it captures the traffic you want. 

Above all, don’t get impatient when doing your research.  This is hard work and requires a pretty detailed level of understanding.  You will often need hours of time to solve a single problem especially if you are not intimately familiar with the protocol or process in question. 

With that, I am signing off for 2011.  Hope everyone has a safe and happy 2012.