Detecting SSH brute forcing with Zeek

In this blog post, we will explore how Zeek detects SSH brute forcing. We will explore the SSH handshake to understand how it works. Next, I will demonstrate several test cases of Zeek detecting SSH brute forcing. Finally, this post will lay down the foundation to implement active defense controls with Zeek in future posts.

Goals

  • Detect SSH brute forcing with Zeek
  • Analyze PCAPs with Zeek
  • Analyze live traffic with Zeek
  • Touch on Zeek scripting
  • Zeek policies

Background

What is Zeek?

Zeek(formerly known as Bro) is a passive, open-source network traffic analyzer. It is primarily a security monitor that inspects all traffic on a link in depth for signs of suspicious activity. More generally, however, Bro supports a wide range of traffic analysis tasks even outside of the security domain, including performance measurements and helping with troubleshooting.

What is a brute force attack?

In cryptography, a brute-force attack consists of an attacker submitting many passwords or passphrases with the hope of eventually guessing correctly. The attacker systematically checks all possible passwords and passphrases until the correct one is found.

This blog post will use the phrase “brute force” to reference brute force and dictionary attacks.

SSH connection explained

High-level overview of SSH version 2

  • TCP handshake
  • Client and server exchange
  • Client and server exchange list of supported encryption and compression algorithms
  • Diffie-Hellman exchange
  • SSH tunnel
  • TCP teardown

Step 1: TCP handshake

Packets 1, 2, and 3 are associated with the TCP handshake (Figure 1). Packet 1 is a TCP SYN (Synchronize) sent from the client (Kali Linux – 192.168.228.143) to initiate a connection with the SSH server ( Ubuntu Server 18.04 – 192.168.228.139). The SSH server responds with an SYN, ACK stating it would like to start a connection (Synchronize) and an ACK (acknowledgment) to the client’s request to start a connection. The client responds with an ACK (acknowledgment) to the SSH server stating it received its message to start a connection. After this sequence of events, we have established a TCP connection between the client and the SSH server.

Figure 1: TCP handshake

Step 2: Client and server protocol exchange

Packets 4, 5, 6, and 7 are packets associated with the exchange of SSH protocol information (Figure 2). Packet 4 is the SSH client sending its protocol information which is “SSH-2.0-OpenSSH_7.9p1 Debian 9” (Figure 3). This protocol information is stating the SSH client is using SSH version 2 and using the OpenSSH client version 7.9p1 from a Debian 9 system. Packet 5 is an ACK (acknowledgment) packet from the SSH server, saying it received the client’s message.

Packet 6 is the SSH server sending its protocol information which is “SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.3” (Figure 4). This protocol information is stating the SSH server is using SSH version 2 and the SSH server is using OpenSSH version 7.6p1 for Ubuntu based systems. Packet 7 is an ACK (acknowledgment) packet from the client to the SSH server, saying it received the server’s message.

Figure 2: Client and server protocol exchange

Figure 3: Client’s protocol information

Figure 4: Server’s protocol information

Step 3: Client and server key exchange init

Packets 8, 9, 10 and 11 are packets associated with the key exchange init (Figure 5). Packet 8 is the SSH server sending its key exchange init which is a list of encryption and compression algorithms (Figure 6).  This list of encryption and compression algorithms tells the client which algorithms the server supports for communication. Packet 9 is an ACK (acknowledgment) packet from the client to the SSH server, saying it received the server’s message.

Packet 10 is the SSH client sending its key exchange init which is a list of encryption and compression algorithms (Figure 7). This list of encryption and compression algorithms tells the server which algorithms the client supports for communication. Packet 11 is an ACK(acknowledgment) packet from the SSH server to the client, saying it received the client’s message.

Figure 5: Client and server key exchange init

Figure 6: Server key exchange init

Figure 7: Client key exchange init

Step 4: Client and server Diffie-Hellman group exchange

Packets 12, 13, 14, 15, and 16 are the packet associated with the Diffie-Hellman exchange (Figure 8). To be honest, I would butcher an explanation but I will provide someone else’s explanation(Credit goes to [Ultimate Guide] How SSH works?):

The classic procedure of Diffie – Hellman algorithm to develop a session key is discussed below step-by-step:

  1. Firstly, both client and server agree on a large prime number, which will serve as a seed value.
  2. Then, both parties agree on an encryption generator (typically AES), which will be used to manipulate the values in a predefined way. This encryption generator method is the one which will be supported by both server and client.
  3. Independently, each party comes up with another prime number which is kept secret from the other party. This number is used as the private (secret) key for this interaction (this private key is different than the private SSH key used for authentication).
  4. The generated private key (the secret to themselves), the encryption generator (common to both), and the shared prime number (common to both) are used to generate a public key, but which can be shared with the other party.
  5. Both participants then exchange their generated public keys.
  6. The receiving party uses their own private key, the other party’s public key, and the original shared prime number to compute a shared secret key. Although this is independently computed by each party, using opposite private and public keys, will result in the same shared secret key.
  7. The shared secret is then used to encrypt all communication that follows. This key is again known as a session key or symmetric key.

Figure 8: Diffie-Hellman key exchange

Figure 9: Client – Diffie-Hellman key exchange init

Figure 10: Server – Diffie-Hellman key exchange reply

Step 5: Initialize SSH tunnel

Packets 17, 18, 19, 20, 21, and 22 are packets associated with SSH tunnel communication between the client and server (Figure 11). The SSH server and client will communicate using this SSH tunnel. Periodically, the server or client will send an ACK to acknowledge receiving data. This communication will continue until a party decides to disconnect. This step is where the authentication between the client and SSH server occurs and where Zeek determines if the authentication was successful.

Figure 11: SSH tunnel communication

Step 6: TCP teardown

Packets 60, 61, 62, and 63 are packets associated with TCP teardown (Figure 12). Packet 60 is sent from the client to the SSH server, which means the client wishes to disconnect. Packet 61 is an ACK (acknowledgment) packet from the SSH server to the client, saying it received the client’s request to disconnect. Packet 62 is a FIN, ACK packet from the SSH server stating it is closing the connection on its end. Packet 63 is an ACK (acknowledgment) packet from the client to the SSH server, saying it received the SSH server’s message to close the connection.

Figure 12: TCP teardown

How Zeek detects SSH brute forcing

You may be wondering how Zeek detects a failed login versus a successful login, and I wondered this as well. I did some Google searching and PCAP analysis with no clear answer, but then I came across this Zeek mailing list thread, which stated the Zeek SSH analyzer heuristically makes a guess about a successful login based on the amount of data returned from the server (the default is 5k). It’s far from 100% accurate, but in my environment has been extremely useful.  Recently, I’ve been looking through some SSH traces trying to find a more refined heuristic because if someone logs in and then logs out again right away, it’s likely the server will cross the byte count threshold and a successful connection will be marked as unsuccessful.  If you have any ideas for how to make that happen, I’d be glad to hear. that said the script detects brute forcing based on the server length field.

So when a user logs in successfully, the SSH server will respond with a packet size of 5k. Therefore, Zeek will mark the “auth_success” attribute as “T” in the SSH.log. Please keep in mind, this packet size may NOT be true for all environments and results may vary. The Zeek SSH brute forcing script monitors SSH events for multiple events that have “auth_success” set to “F”, meaning, a brute force attempt.

Within the SSH brute forcing script contains the following variables “password_guesses_limit” and “guessing_timeout”. The “password_guesses_limit” is the threshold of failed logins that need to be detected. The “guessing_timeout” variable defines a window time for how many failed logins have to happen in a window to generate an alert.

The SSH brute forcing script is making the following assumptions: A failed login will generate a packet from the SSH server that is NOT equal to 5k, which will be recorded as “F” for “auth_success” in the SSH.log. Having the “auth_success” attribute set to “F” means it was a failed login attempt. The script is monitoring SSH events for multiple failed login events that surpass a threshold (password_guesses_limit) in a defined time window (guessing_timeout).

Test case 1: Detecting SSH brute forcing in PCAP

Install/Setup Zeek on Kali Linux

  1. Open terminal
  2. apt-get update -y
  3. apt-get install bro broctl bro-aux -y
  4. bro -V

Add SSH brute forcing script

  1. cat >>  /usr/share/bro/site/ssh-detect.bro << 'EOF'
    @load protocols/ssh/detect-bruteforcing
    
    redef SSH::password_guesses_limit=3;
    
    hook Notice::policy(n: Notice::Info)
      {
      if ( n$note == SSH::Password_Guessing )
                    add n$actions[NOTICE::ACTION_LOG];
      }
    EOF
  2. echo "@load ssh-detect.bro" >> /usr/share/bro/site/local.bro

Download PCAP

  1. cd ~/Desktop
  2. mkdir bro_logs
  3. cd bro_logs
  4. wget https://github.com/bro/bro/raw/master/testing/btest/Traces/ssh/sshguess.pcap

Run Zeek against PCAP

  1. bro -C -r sshguess.pcap local
    1. -C: Ignore invalid checksums
    2. -r: Analyze this PCAP
    3. local: Use site/local.bro to load scripts to analyze this PCAP

Review Zeek logs

  1. cat notice.log
  2. cat notice.log | bro-cut ts uid src dst msg

Test case 2: Zeek detect live attack with TCPreplay

Add SSH brute forcing script

  1. cat > /usr/share/bro/site/ssh-detect.bro << 'EOF'
    @load protocols/ssh/detect-bruteforcing
    
    redef SSH::password_guesses_limit=3;
    redef SSH::guessing_timeout=60 mins;
    
    event NetControl::init()
      {
      local debug_plugin = NetControl::create_debug(T);
      NetControl::activate(debug_plugin, 0);
      }
    
    hook Notice::policy(n: Notice::Info)
      {
      if ( n$note == SSH::Password_Guessing )
        NetControl::drop_address(n$src, 60min);
        add n$actions[Notice::ACTION_DROP];
        add n$actions[Notice::ACTION_LOG];		
      }
    
    EOF

Start Zeek with Broctl

  1. Back on Kali Linux
  2. Open a terminal
  3. echo "redef ignore_checksums = T;" >> /usr/share/bro/site/local.bro
    1. Disables checksum checking
  4. broctl deploy
  5. broctl status
  6. cat /var/log/bro/current/loaded_scripts.log | grep ssh-detect

TCPreplay

  1. cd ~/Desktop/bro_logs
  2. tcpreplay -i eth0 sshguess.pcapng
    1. -i: Interface to replay packets on

Review Zeek logs

  1. cat ssh.log | bro-cut ts uid id.orig_h id.resp_h auth_attempts auth_sucess
  2. cat /var/log/bro/current/notice.log
  3. cat /var/log/bro/current/notice.log | bro-cut ts uid src dst msg
  4. cat /var/log/bro/current/netcontrol.log
  5. cat /var/log/bro/current/netcontrol_drop.log

Test case 3: Zeek detect live attack with Hyrda

WARNING

This section was purposely put here to show the limitations of Zeek. This section will demonstrate an SSH brute forcing attack that is NOT detected by Zeek.

WARNING

Spin up SSH server

  1. Spin up an Ubuntu VM
  2. Open a terminal
  3. apt-get update -y && apt-get install openssh-server -y
  4. sed -i 's/#MaxAuthTries 6/MaxAuthTries 100/g' /etc/ssh/sshd_config
    1. FOR TESING PURPOSE ONLY
  5. systemctl restart ssh
  6. systemctl status ssh
  7. netstat -tnlp
    1. Make sure SSH is listening on port 22
  8. ip a
    1. Get IP address

Start Zeek with Broctl

  1. Back on Kali Linux
  2. Open a terminal
  3. broctl deploy
  4. broctl status
  5. cat /var/log/bro/current/loaded_scripts.log | grep ssh-detect

Attack SSH server with Hydra

  1. ping <IP addr of SSH machine>
  2. nmap -p22 -sV <IP addr of SSH machine>
  3. hydra -l root -P /usr/share/wordlists/metasploit/unix_passwords.txt <IP addr of SSH machine> -t 5 ssh
    1. -l: Username to use
    2. -P: File containing a list of passwords
    3. -t: Concurrent threads to use to brute force this machine
    4. ssh: Brute force an SSH login

Review Zeek logs

  1. cat /var/log/bro/current/netcontrol.log
    1. No sign of an event being sent to NetControl about SSH brute forcing
  2. cat /var/log/bro/current/notice.log
    1. File does not exist, so no notices were created by our Zeek script

Why wasn’t this detected?

As stated in the “How Zeek detects SSH brute forcing” section, the SSH brute forcing script is looking for the “auth_sucess” attribute to be set to “F” multiple times. However, since Zeek is unable to detect if the authentication was successful or not, it places a “-” instead. If you look at the photo below you will see this:

Figure 13: Hydra SSH brute force – ssh.log

 

Figure 14: sshguess.pcap – ssh.log

You may be asking why Zeek can detect “auth_sucess” in test case 1 and 2 but not in test case 3. As stated in the “How Zeek detects SSH brute forcing” section, the SSH analyzer looks at the packet length to detect if it failed to login or not. From the e-mail thread mentioned above, I dived into the PCAPs one last time to search for the truth. I discovered the length field in the sshguess.pcap (Figure 15) was in cleartext but not in the Hydra brute force pcap, it was encrypted (Figure 16). Since this field is encrypted, the SSH analyzer sets the “auth_success” attribute to “-“, which means it could not determine if the login was successful or not.

Figure 15: SSH protocol header in sshguess.pcap

 

Figure 16: Hydra brute force PCAP

Future work

This blog post is creating the foundation for a future blog post. That blog post will be using Zeek + NetControl framework to implement active defense controls. Simply put, a future blog will have Zeek detecting malicious activity and will utilize the NetControl framework to block the malicious activity with IPtables.

References

8 thoughts on “Detecting SSH brute forcing with Zeek

  1. n00b says:

    sorry noob question,
    why not rely solely on fail2ban ?

    • spartan2194 says:

      This is actually a great question! Zeek is a detection control but Fail2Ban is a preventative control. Let’s say, for example, your company has an AWS cloud instance with SSH. Someone within your company tries to login with the wrong password too many times. With Fail2Ban it would block ALL SSH access for all users for your company. With an environment like this, Fail2Ban may not be the best option.

      Second and probably more importantly, Zeek provides context around the SSH brute force attempt. In this scenario, let’s assume your company network only has Zeek setup to monitor the network. You get an alert that computer A is attempting a brute force attack against computer B. With Zeek you can investigate this incident in much greater detail. Maybe you see Computer A making outbound connections to a malicious domain.

      In security, we want to strive to have preventative controls over detection when appropriate but prevention is hard.

  2. Rozy says:

    Hi, iam a student from indonesia
    Can you make a tutorial on how to detect syn flood with zeek
    Thank you

    • spartan2194 says:

      Hey Rozy,

      Unfortunately I don’t take requests on blog posts to make. I create blog posts based on the cool things I am doing. However, I would recommend searching for “zeek syn flood script” on Google. There are plenty of scripts already made to detect this. Thanks for reading!

  3. Priyanka says:

    Hello!!
    I have an error while running
    zeek -C -r sshguess.pcap local
    it says
    1427726703.872095 error: packet_filter/Log::WRITER_ASCII: cannot open packet_filter.log: Permission denied

    Please tell me how to fix this?

    • spartan2194 says:

      Hey Priyanka,

      It seems you have a permissions error because Zeek doesn’t have the proper permissions to write to the log file mentioned in your comment. Please try running the command in your comment with “sudo”. If the command executes successfully, it’s a permissions error and you need to take appropriate actions such as ensuring Zeek can write to the current directory.

  4. Vincenzo says:

    Exists some script to improve bruteforce ssh zeek?

    • spartan2194 says:

      Hey Vincenzo,

      Unfortunately, I do not know of an updated Zeek script to detect SSH bruteforces in the updated SSH protocol. I would look into tools like Fail2Ban to detect this on the host.

Leave a Reply

Your email address will not be published. Required fields are marked *