How to crack ssh known_hosts files with hashcat

If you just want to know how to use the script, skip to the example usage section below.

Background

The OpenSSH client uses a file called known_hosts to track the fingerprint for previously used ssh servers. This can help the SSH client detect when a man in the middle attack is taking place. If an attacker was to try this attack, the user’s client would show a serious warning and refuse to connect. This is done to stop the user from connecting to an attacker’s system. In my experience, Ubuntu and Debian servers seem to have the “HashKnownHosts” setting on by default. This setting does not seem to be the defacto default setting, so it may be disabled by the OS or vendor.

in 2005 a team from MIT wrote a paper about the potential threat of an SSH based worm. Their theoretical SSH worm would replicate by locating other SSH servers through the known_hosts file. To defend against this threat the OpenSSH team added a new option “HashKnownHosts” in version 4.0. This effectively feature hashes the IP and domains that are stored in the known_hosts file and effectively killed off the threat they had described. However, they did not foresee the GPU password cracking abilities that we have today.

Bruce Schneier also had a blog post about the issue and their paper.

Example known_hosts hashing configuration enabled in /etc/ssh/ssh_config:

    HashKnownHosts yes

The known_hosts file can help Red Teams

During an engagement I ran into a server and network that had detailed network monitoring. I had a shell, but before doing a network scan I wanted to see what other systems I could try to connect to. If an admin has already connected to an SSH server, there is a good chance this wont raise an alarm.

Much like the SSH worm idea, Red Teamers and attackers will use these files to detect other hosts on a given network. This is usually preferred as a network scan with nmap and masscan is very noisy and may be detected. However, if the file is hashed, they aren’t as useful, unless you can find a way to crack the hashes.

known_hosts file format

A non hashed known_hosts file example:

192.168.10.12 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA01yz1a/UkkdKsqNIfALi13OmJ305weWukUtdG5WY2xKBzc3UDqBTVndbpzMEeXl/A/4SAPdc/dUUVNYJWHc8SvcFa2n+NXduq6UPmimJYxX0glHLql9rhX9X6BrpYq93J08tcdPJlS88AF86oL0HRk1l3whN8x7v62UfPSF3/apihx5PQVEYI0rL47wi6gYPRb70CiEn1MCvIJLeyBaIjvhZ+LKsXhNafahGo36Ck7Tf2iqTNuuy56U/ijt0MHg3kOwEecVVbWS3RSASQCfu345BK2a4soeIG1JpfTakz23Cb5T76wBM63uUDvFmmjn+ljZlNafN/AQLwIfYyxQ/pw==

a hashed known_hosts example:

|1|wlPQdgFoYgYsqG6ae20lYopRLPI=|p61txQKmb+Hn49dsD+v0CNuEKd4= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzhZmG33G/3FG3vm0eDdyX1u++i0ceakIkJNgDxVVy6MpodRrpwqXXQj8/OGT
Iwb4YpRXGuL3236IkGugI9GUgFd00UNjMSMt3pqob4hKsEzADl7YfZeV1X7X0b617nze0otdO7TwDMlQ/5KWUwdUoxg50VfpieTzcOpUN/G4J159iKZ41iSF7o4vI+fYisX8y5rJ1BRbt1HO0Gi7w9HZ8tN0B
0glM6JKyoE8TjvbZAeD9PWIWp9JpG1KTY4yXTV1B1CyvtxjRqTMm8mcb+gSGGvv6mSlWCNxJnlXhp91F2GtmgzKsE3FjcMUfkn3c0+P0bKaR8L3GtbyaXJmtDX4xQ==

This next example known_hosts file with a single IP address. These commands will explain a little bit more on how these hashes are created (these commands were taken from this post):

First, lets see what is in the known_hosts file:

$ cat ~/.ssh/known_hosts
|1|GOOqYoIUqAQ4Qsun3Lb9yhEY7bc=|wFUz1PicR/NNbqrgKz4NlClxDdI= ecdsa-sha2-nistp256 AAAAE2V---snip---

The first base64 encoded string is used as the salt:

$ echo -n "GOOqYoIUqAQ4Qsun3Lb9yhEY7bc=" | base64 -d | xxd -p
18e3aa628214a8043842cba7dcb6fdca1118edb7

The second string, is the IP address of the server.

$ echo -n "192.168.86.245" | openssl sha1 -mac HMAC -macopt hexkey:18e3aa628214a8043842cba7dcb6fdca1118edb7 | awk '{print $2}' | xxd -r -p | base64
wFUz1PicR/NNbqrgKz4NlClxDdI=

My first approach at cracking the known_hosts hashes

I wanted to start off by clarifying that I am not the first to do this attack. I’ve seen at small handful of posts that describe the technique. This attack also only seems to work if the user logged into the SSH server with the IP address vs. using the domain name.

My first approach with this was to create a cracking dictionary with every possible internal IP address in it ex: 10.0.0.0/8. The file was big, but not unmanageable. the 10.0.0.0/8 private block is around 16 million IP addresses. However, the entire internet would be around 4.3 billion. My first approach worked fairly well, but I found a better way after doing some googling.

The optimized approach, mask attacks

I found the following post on the hashcat forums with information about using a mask attack for brute-forcing hashes for all IPv4 addresses. Hashcat mask attacks are used often as they can be better tuned for cracking vs. the brute force method. In our case it is easier to use a mask attack as you don’t need to generate a 4.3 billion IP address dictionary file. The IPv4 hcmask file included in this repository was originally downloaded from this pastebin post, but I’ve included it here to save you time.

Can OpenSSH find a solution to solve defend against this attack?

It doesn’t seem like there would be a clear solution. If they used a more expensive hashing algorythm like bcrypt, the GPUs could still crack the entire IPv4 address space for a single hash in ~50 hours with a single Nvidia 1080 GTX ti. A single card can do about 23223 bcrypt Hashes/second per this benchmark (4,294,967,296 ip addresses / 23223 hashes a second / 60 second per minute / 60 minutes per hour = 51.3 Hours). Also, if bcrypt was used, this could cause slowness or performance issues potentially, especially for lower powered embedded devices.

Example usage

The python script used for this method can be found here: https://github.com/chris408/known_hosts-hashcat

#This is an example of what a hashed known_hosts file looks like:
cat ~/.ssh/known_hosts
|1|wlPQdgFoYgYsqG6ae20lYopRLPI=|p61txQKmb+Hn49dsD+v0CNuEKd4= ssh-rsa AAAAB3NzaC1yc2EAA--snip--==

#This will convert the hashed known_hosts file into a format that hashcat can attempt to crack:
python3 kh-converter.py ~/.ssh/known_hosts
a7ad6dc502a66fe1e7e3d76c0febf408db8429de:c253d076016862062ca86e9a7b6d25628a512cf2

#To save the output from kh-converter.py, lets redirect stdout to a file:
python3 kh-converter.py ~/.ssh/known_hosts > converted_known_hosts

#Finally, to crack the "HMAC-SHA1 (key = $salt)" hashes, use the following hashcat command:
hashcat64.bin -m 160 --quiet --hex-salt converted_known_hosts -a 3 ipv4_hcmask.txt 
a7ad6dc502a66fe1e7e3d76c0febf408db8429de:c253d076016862062ca86e9a7b6d25628a512cf2:10.180.6.49

As you can see from the hashcat output above, the IP address was found to be 10.180.6.49. Note: This cracking attempt was completed in about 1 minute on a single Nvidia 1080 GTX GPU.

Thanks

Thanks to Jason and Eden for the suggestions on this post.