back to this course. In the previous video, we started talking about
detecting brute force password guessing or credential stuffing attacks over the network. In that video, we talked about one of the easy cases where we have access to the various commands being used to authenticate to FTP; and so, we are able to track the status of those commands and see the message from the server saying that an authentication attempt had failed. In this video, we're going to look at a bit more of a realistic case, one where we don't have that level of data. We'll be looking at SSH. Our problem with SSH is that SSH is designed to be an encrypted protocol. SSH, that client and the server set up a shared encryption key using asymmetric cryptography; and then with that shared encryption key, they're able to encrypt everything else that they say to one another. Since things like authentication are encrypted, we're not able to take a look and see what user accounts the user is trying to log into, what password they provide, and whether or not they succeeded. In general, that's a good thing. However, when we're trying to determine if we're a victim of a credential stuffing attack using solely network traffic, that makes our lives a little bit more difficult. However, it's still possible to detect a potential credential stuffing attack, it just takes a little bit more work with SSH. Once again, we've got Wireshark open here; and in this case, we have a file with four login attempts to SSH. The first and last are successful and the middle two are unsuccessful using an incorrect password. If we look at the data that we can see in our network traffic, there's not a lot of use here. For example, I can do follow TCP stream, to see the contents of the TCP stream. We see the handshake where they set up parameters, which is potentially interesting if you've gotten insecure SSH, client, and server. But if it's secure, this is all public data anyway. Then after that we just have gibberish, because we're trying to interpret encrypted data as something that actually make sense. If I flick through the streams here, we see more of the same. I just told you that the first and the last streams or authentication attempts in this capture are the two successful ones. As I flick back and forth through the streams, that might give you a hint about how we're going to differentiate successful logins and failed ones. We've got a successful one here and a failed one here. They're much different in length. Because if you have a successful login, you're connected to the system, you're able to interact with it and actually do something. In this case, when we've got a successful one here, there's not actually much done on the system, but just the fact that we have a successful authentication; and then, we get the banner from the server saying that the login was successful, is enough to create a fairly long packet capture. When we go to a failed one where we tried to log in, it didn't work. Then the client immediately terminated the connection rather than trying to enter another password; we have a much shorter capture. This difference in length can let us guess whether or not a connection was successful or not. We've got a vague feeling here by looking just at the streams, but we can actually get something a little bit more concrete using Wireshark. Wireshark has this statistics area and if we choose conversations, we see that there's four TCP conversations here, that makes sense. SSH occurs over TCP and we've got four connection attempts. Here, we can see the IP source address, source port, destination address, destination port. We see high number of ports here for our client and a consistent port 22 for the server. We see the number of packets sent. Here, we can already see some differences. We've got 25 packets sent for a failed attempt, and closer to 40 for a successful one. We see that the number of bytes sent is much larger for a successful attempt than for a failed one, etc. What we're going to focus on here is the bytes from the server to the client, because notice that if we go into the client server bytes, there's not much variation. Got about a little under 3,000 for a successful attack and a little under 2,400 for an unsuccessful one. We'd have to tune our threshold very tight to differentiate those and so if there was any variation for some reason, then we'd get a false positive or a false negative. However, we look at the server in this case, we see over 8,000 bytes sent from the server to the client and closer to 2,000 for client to server. A bigger gap gives us a little bit more room for error. That's what we're going to be focusing on in our Python analysis. I'm going to minimize this and we'll go back to our code. This is the same file that we looked at in the previous video, here's our FTP analysis. Down here we talk about making the decision to go to FTP or SSH and then we didn't talk through this, but this section here is just designed to print the results that we saw in the previous video and that we'll see in this one as well. But our focus here is going to be on the SSH traffic. Assuming that we have a packet that's going to the server, and assuming that that server is set up, normally, we know that its destination port is going to be unknown. That's the high number client port, but this source port from server to client is going to be port 22, if we've got a server to client transmission. That's the one we care about because we just looked in Wireshark and saw that the server to client byte counts are widely different for a successful, non-successful authentication in this particular case and we'll talk a bit more about why in a moment but the client to server one's not that different. Here in our SSH analysis function, we should only be looking at traffic from the server to the client and so we'll pull out a few different pieces of information. We know that the server IP address will be the source IP, the Client IP will be the destination IP, and so we're going to use that same keying as we did for FTP, saying going from the client to the server so from CIP to SIP. In practice, it's going to be from the destination IP to source IP. We also know that the only port that really provides us any information here is the destination port, because the server port is always going to be 22, the destination port is going to vary and be a high number port and so we're looking at the length of the traffic that we're sending. Easiest way to get that is to take a look inside our packet and pull out the length field of an IP packet. That'll tell us the amount of data stored in that IP packet and if we add 14 we'll get the total amount including the headers that aren't included in that length value, so L here is the length of the packet server sending to the client. Now we have an if statement and the reason why is, we want to be able to say whether or not a connection is closed and failed and we could use an easy way to do that. With SSH, if things go properly, you should have a nice tear down using a fin, thin AC, etc. If the connections died for some reason, there should be a packet from the server to the client saying fin or having an f flag in its TCP packets and so we're going to test for that. If we have that f we're tearing down and we're going to take the records that we're building and move it to the failed SSH category. Otherwise, we're going to add the length that we've just observed to a dictionary that we're building similar to what we did with the FTP. Let's do that second case first here. Every packet that's not tearing down the connection, we're first going to see if we've already got this connection in our dictionary of SSH connection. At this point, we're looking at a pair of IP addresses, source, and destination. If we don't already have that in our dictionary, we're going to add it and assign an empty dictionary to it. Then we need to determine whether or not this particular port is in that dictionary. That port again is going to be the client port because that's the high number port that varies, allowing us to differentiate different SSH connections from one another. Because theoretically, you could have multiple different SSH sessions running at the same time from the same computer and so they'd both be going to the same port but have different source ports. If the port already exists in that dictionary, we're going to take its current length value that's stored and add in the length of our new packet. Otherwise, we're going to test to see if there is a SYN flag set in our packet, because that means that we're setting up a new SSH connection. This matters because I just said a few moments ago that we're testing for the FIN flag from the server to the client. That's not actually the last packet that the server sends. In fact, there's a chance that the server will send a FIN flag, the client will send a FIN ACK, and then the server will send an ACK, acknowledging that final FIN ACK and tearing down the connection. In this case, we've got the potential that we have a packet coming from a connection we've already decided to ignore. To ensure that we don't have that case, we're going to look for a SYN flag set in the servers' packet, because that means we're at the start of a connection and not the end. If this is a brand new connection, we'll add it to our dictionary and specify that it's current length is the length of this packet. Regardless of if we're successful or unsuccessful with authentication, this will happen several times as the client and server negotiate all the terms of the connection, and then the client sends in its username and password. Then if we've got a successful connection, there's even the possibility of the client's going to do something with that connection. Perform some commands on the server. But eventually, we'll get to the point where that connection gets torn down using FINs and facts. If we've got a FIN packet from the server, then we know that we need to take this particular connection record out of our SSH cons, which tracks existing live connections and puts it in our failed SSH one, if it was a failed connection attempts. We're going to pop the record for a particular IP pair and the client port and the result will be stored in b, so that b should be the length of the packets that had been sent from the server to the client in this connection. We're going to add in the length of our current packet, and then we're going to test it against a threshold. In this case, I'm using a threshold of 5,000, but this might have to vary based off of your implementation. I'm using 5,000 here because that's what's used in Bro/Zeke for this exact same purpose. However, depending on how your server is configured, banners, etc, you might need it to go lower or higher. As we saw here, 5,000 is a nice safe value in this case. The reason why in this case is the server I'm connecting to is default Vanilla Ubuntu server and so it's banner message is quite long. That gives us that nice long 8,000 bytes being sent from the server because of all that information it provides to the user about like last login, information on the system, etc. If your servers have been modified to have a different banner that's shorter, you might need a different threshold. But if the bytes from the server to the client is less than that threshold, we've got a good chance that we're looking at a failed login attempt. If so, we're going to check our failed SSH dictionary to see if our key, so that IP pair is already there. If so, we're going to increment the number of failed login attempts. Otherwise, we'll create a new entry in the dictionary for that failed login attempt. Important to notice here, a major difference between this and our FTP version, and that's that we're not tracking usernames. The reason why is we can't. That authentication occurs within the encrypted connection and so we can't see what username was being sent to the server. The best we can do is say, okay, there's a failed login from this user or from this IP address to this IP address. That at least helps provide us some information about whether we might have an attempted brute force or credential stuffing attack. If we can identify the target of that attack, we can look at that particular server and its records to see which user was the target of that attack. Let's give this version of the code a run as well. In this case, note I've commented out the FTP related stuff and we'll look at the SSH packet capture now. If I do Python, BruteForceNetworkDetection.py, hit Enter and we see that we have two failed login attempts from this IP address to this IP address. You can see that we're collecting here on the client's local network to the 192.168 local address. However, if we were doing this defensively, we might see something like a login attempt from a public-facing IP to a private one instead. Based off of this with only two failures, as we discussed before, two failures might not indicate that we've got an attempted attack. However, if I was seeing 200 or 2,000 or whatever, that might be an indication that something's going wrong. It depends on how often that system should be accessed and you can also look at should this IP address have access to that system. If not, any number of failed login attempts or even successful ones is indication that something's wrong and more investigation is needed. This closes up our discussion of brute force detection over the network. Previous video, we looked at an easy case with FTP. Now we looked at one based a little bit more on heuristics and guessing for SSH. Thank you.