'<target host> -p <target port>') parser.add_option('-H', dest='tgtHost', type='string', \ help='specify target host') parser.add_option('-p', dest='tgtPort', type='int', \ help='specify target port') (options, args) = parser.parse_args() tgtHost = options.tgtHost tgtPort = options.tgtPort if (tgtHost == None) | (tgtPort == None): print parser.usage exit(0) Next, we will build two functions connScan and portScan. The portScan function takes the hostname and target ports as arguments. It will first attempt to resolve an IP address to a friendly hostname using the gethostbyname() function. Next, it will print the hostname (or IP address) and enumerate through each individual port attempting to connect using the connScan function. The connScan function will take two arguments: tgtHost and tgtPort and attempt to create a connection to the target host and port. If it is successful, connScan will print an open port message. If unsuccessful, it will print the closed port message. import optparse from socket import * def connScan(tgtHost, tgtPort): try: connSkt = socket(AF_INET, SOCK_STREAM) connSkt.connect((tgtHost, tgtPort)) print '[+]%d/tcp open'% tgtPort connSkt.close() except: print '[-]%d/tcp closed'% tgtPort def portScan(tgtHost, tgtPorts): try: Building a Port Scanner 35 tgtIP = gethostbyname(tgtHost) except: print "[-] Cannot resolve '%s': Unknown host"%tgtHost return try: tgtName = gethostbyaddr(tgtIP) print '\n[+] Scan Results for: ' + tgtName[0] except: print '\n[+] Scan Results for: ' + tgtIP setdefaulttimeout(1) for tgtPort in tgtPorts: print 'Scanning port ' + tgtPort connScan(tgtHost, int(tgtPort)) Application Banner Grabbing In order to grab the application banner from our target host, we must first insert additional code into the connScan function. After discovering an open port, we send a string of data to the port and wait for the response. Gathering this response might give us an indication of the application running on the target host and port. import optparse import socket from socket import * def connScan(tgtHost, tgtPort): try: connSkt = socket(AF_INET, SOCK_STREAM) connSkt.connect((tgtHost, tgtPort)) connSkt.send('ViolentPython\r\n') results = connSkt.recv(100) print '[+]%d/tcp open'% tgtPort print '[+] ' + str(results) connSkt.close() except: print '[-]%d/tcp closed'% tgtPort def portScan(tgtHost, tgtPorts): try: tgtIP = gethostbyname(tgtHost) except: 36 CHAPTER 2: Penetration Testing with Python print "[-] Cannot resolve '%s': Unknown host" %tgtHost return try: tgtName = gethostbyaddr(tgtIP) print '\n[+] Scan Results for: ' + tgtName[0] except: print '\n[+] Scan Results for: ' + tgtIP setdefaulttimeout(1) for tgtPort in tgtPorts: print 'Scanning port ' + tgtPort connScan(tgtHost, int(tgtPort)) def main(): parser = optparse.OptionParser("usage%prog "+\ "-H <target host> -p <target port>") parser.add_option('-H', dest='tgtHost', type='string', \ help='specify target host') parser.add_option('-p', dest='tgtPort', type='string', \ help='specify target port[s] separated by comma') (options, args) = parser.parse_args() tgtHost = options.tgtHost tgtPorts = str(options.tgtPort).split(', ') if (tgtHost == None) | (tgtPorts[0] == None): print '[-] You must specify a target host and port[s].' exit(0) portScan(tgtHost, tgtPorts) if __name__ == '__main__': main() For example, scanning a host with a FreeFloat FTP Server installed might reveal the following information in the banner grab: attacker$ python portscanner.py -H 192.168.1.37 -p 21, 22, 80 [+] Scan Results for: 192.168.1.37 Scanning port 21 [+] 21/tcp open [+] 220 FreeFloat Ftp Server (Version 1.00). In knowing that the server runs FreeFloat FTP (Version 1.00) this will prove to be useful for targeting our application as seen later. Building a Port Scanner 37 Threading the Scan Depending on the timeout variable for a socket, a scan of each socket can take several seconds. While this appears trivial, it quickly adds up if we are scanning multiple hosts or ports. Ideally, we would like to scan sockets simultaneously as opposed to sequentially. Enter Python threading. Threading provides a way to perform these kinds of executions simultaneously. To utilize this in our scan, we will modify the iteration loop in our portScan() function. Notice how we call the connScan function as a thread. Each thread created in the iteration will now appear to execute at the same time. for tgtPort in tgtPorts: t = Thread(target=connScan, args=(tgtHost, int(tgtPort))) t.start() While this provides us with a significant advantage in speed, it does present one disadvantage. Our function connScan() prints an output to the screen. If multiple threads print an output at the same time, it could appear garbled and out of order. In order to allow a function to have complete control of the screen, we will use a semaphore. A simple semaphore provides us a lock to prevent other threads from proceeding. Notice that prior to printing an output, we grabbed a hold of the lock using screenLock.acquire(). If open, the semaphore will grant us access to proceed and we will print to the screen. If locked, we will have to wait until the thread holding the semaphore releases the lock. By utilizing this semaphore, we now ensure only one thread can print to the screen at any given point in time. In our exception handling code, the keyword finally executes the following code before terminating the block. screenLock = Semaphore(value=1) def connScan(tgtHost, tgtPort): try: connSkt = socket(AF_INET, SOCK_STREAM) connSkt.connect((tgtHost, tgtPort)) connSkt.send('ViolentPython\r\n') results = connSkt.recv(100) screenLock.acquire() print '[+]%d/tcp open'% tgtPort print '[+] ' + str(results) except: screenLock.acquire() print '[-]%d/tcp closed'% tgtPort finally: 38 CHAPTER 2: Penetration Testing with Python screenLock.release() connSkt.close() Placing all other functions into the same script and adding some option parsing, we produce our final port scanner script. import optparse from socket import * from threading import * screenLock = Semaphore(value=1) def connScan(tgtHost, tgtPort): try: connSkt = socket(AF_INET, SOCK_STREAM) connSkt.connect((tgtHost, tgtPort)) connSkt.send('ViolentPython\r\n') results = connSkt.recv(100) screenLock.acquire() print '[+]%d/tcp open'% tgtPort print '[+] ' + str(results) except: screenLock.acquire() print '[-]%d/tcp closed'% tgtPort finally: screenLock.release() connSkt.close() def portScan(tgtHost, tgtPorts): try: tgtIP = gethostbyname(tgtHost) except: print "[-] Cannot resolve '%s': Unknown host"%tgtHost return try: tgtName = gethostbyaddr(tgtIP) print '\n[+] Scan Results for: ' + tgtName[0] except: print '\n[+] Scan Results for: ' + tgtIP setdefaulttimeout(1) for tgtPort in tgtPorts: t = Thread(target=connScan, args=(tgtHost, int(tgtPort))) t.start() Building a Port Scanner 39 def main(): parser = optparse.OptionParser('usage%prog '+\ '-H <target host> -p <target port>') parser.add_option('-H', dest='tgtHost', type='string', \ help='specify target host') parser.add_option('-p', dest='tgtPort', type='string', \ help='specify target port[s] separated by comma') (options, args) = parser.parse_args() tgtHost = options.tgtHost tgtPorts = str(options.tgtPort).split(', ') if (tgtHost == None) | (tgtPorts[0] == None): print parser.usage exit(0) portScan(tgtHost, tgtPorts) if __name__ == "__main__": main() Running the script against a target, we see it has an Xitami FTP server running on TCP port 21 and that TCP port 1720 is closed. attacker:!# python portScan.py -H 10.50.60.125 -p 21, 1720 [+] Scan Results for: 10.50.60.125 [+] 21/tcp open [+] 220- Welcome to this Xitami FTP server [-] 1720/tcp closed Integrating the Nmap Port Scanner Our preceding example provides a quick script for performing a TCP connect scan. This might prove limited as we may require the ability to perform additional scan types such as ACK, RST, FIN, or SYN-ACK scans provided by the Nmap toolkit (Vaskovich, 1997). The de facto standard for a port scanning toolkit, Nmap, delivers a rather extensive amount of functionality. This begs the question, why not just use Nmap? Enter the true beauty of Python. While Fyodor Vaskovich wrote Nmap and its associated scripts in the C and LUA programming languages, Nmap is able to be integrated rather nicely into Python. Nmap produces XML based output. Steve Milner and Brian Bustin wrote a Python library that parses this XML based output. This provides us with the ability to utilize the full functionality of Nmap within a Python script. Before starting, you must install Python-Nmap, available at http://xael.org/norman/ python/python-nmap/. Ensure you take into consideration the developer�s notes regarding the different versions of Python 3.x and Python 2.x. 40 CHAPTER 2: Penetration Testing with Python With Python-Nmap installed, we can now import Nmap into existing scripts and perform Nmap scans inline with your Python scripts. Creating a PortScanner() class object will allow us the capability to perform a scan on that object. The PortScanner class has a function scan() that takes a list of targets and ports as input and performs a basic Nmap scan. Additionally, we can now index the object by target hosts and ports and print the status of the port. The following sections will build upon this ability to locate and identify targets. import nmap import optparse def nmapScan(tgtHost, tgtPort): nmScan = nmap.PortScanner() nmScan.scan(tgtHost, tgtPort) state=nmScan[tgtHost]['tcp'][int(tgtPort)]['state'] print " [*] " + tgtHost + " tcp/"+tgtPort +" "+state def main(): parser = optparse.OptionParser('usage%prog '+\ '-H <target host> -p <target port>') parser.add_option('-H', dest='tgtHost', type='string', \ help='specify target host') parser.add_option('-p', dest='tgtPort', type='string', \ help='specify target port[s] separated by comma') (options, args) = parser.parse_args() tgtHost = options.tgtHost tgtPorts = str(options.tgtPort).split(', ') MORE INFORMATION� Other Types of Port Scans Consider a few other types of scans. While we lack the tools to craft packets with TCP options, we will cover this later in Chapter 5. At that time see if you can replicate some of these scan types in your port scanner. TCP SYN SCAN�Also known as a half-open scan, this type of scan initiates a TCP connection with a SYN packet and waits for a response. A reset packet indicates the port is closed while a SYN/ACK indicates the port is open. TCP NULL SCAN�A null scan sets the TCP flag header to zero. If a RST is received, it indicates the port is closed. TCP FIN SCAN�A TCP FIN Scan sends the FIN to tear down an active TCP connection and wait for a graceful termination. If a RST is received, it indicates the port is closed. TCP XMAS SCAN�An XMAS Scan sets the PSH, FIN, and URG TCP Flags. If a RST is received, it indicates the port is closed. Building an SSH BotNet with Python 41 if (tgtHost == None) | (tgtPorts[0] == None): print parser.usage exit(0) for tgtPort in tgtPorts: nmapScan(tgtHost, tgtPort) if __name__ == '__main__': main() Running our script that utilizes Nmap, we notice something interesting about TCP port 1720. The server or a firewall is actually filtering access to TCP port 1720. The port is not necessarily closed as we initially thought. Using a fullfledged scanner like Nmap instead of a single TCP connect scan we were able to discover the filter. attacker:!# python nmapScan.py -H 10.50.60.125 -p 21, 1720 [*] 10.50.60.125 tcp/21 open [*] 10.50.60.125 tcp/1720 filtered BUILDING AN SSH BOTNET WITH PYTHON Now that we have constructed a port scanner to find targets, we can begin the task of exploiting the vulnerabilities of each service. The Morris Worm includes forcing common usernames and passwords against the remote shell (RSH) service as one of its three attack vectors. In 1988, RSH provided an excellent (although not very secure) method for a system administrator to remotely connect to a machine and manage it by performing a series of terminal commands on the host. The Secure Shell (SSH) protocol has since replaced RSH by combining RSH with a public-key cryptographic scheme in order to secure the traffic. However, this does very little to stop the same attack vector by forcing out common user names and passwords. SSH Worms have proven to be very successful and common attack vectors. Take a look at the intrusion detection system (IDS) log from our very own www.violentpython.org for a recent SSH attack. Here, the attacker has attempted to connect to the machine using the accounts ucla, oxford, and matrix. These are interesting choices. Luckily for us, the IDS prevented further SSH login attempts from the attacking IP address after noticing its trend to forcibly produce the passwords. Received From: violentPython->/var/log/auth.log Rule: 5712 fired (level 10) -> "SSHD brute force trying to get access to the system." Portion of the log(s): Oct 13 23:30:30 violentPython sshd[10956]: Invalid user ucla from 67.228.3.58 42 CHAPTER 2: Penetration Testing with Python Oct 13 23:30:29 violentPython sshd[10954]: Invalid user ucla from 67.228.3.58 Oct 13 23:30:29 violentPython sshd[10952]: Invalid user oxford from 67.228.3.58 Oct 13 23:30:28 violentPython sshd[10950]: Invalid user oxford from 67.228.3.58 Oct 13 23:30:28 violentPython sshd[10948]: Invalid user oxford from 67.228.3.58 Oct 13 23:30:27 violentPython sshd[10946]: Invalid user matrix from 67.228.3.58 Oct 13 23:30:27 violentPython sshd[10944]: Invalid user matrix from 67.228.3.58 Interacting with SSH Through Pexpect Lets implement our own automated SSH Worm that brute forces user credentials against a target. Because SSH clients require user interaction, our script must be able to wait and match for an expected output before sending further input commands. Consider the following scenario. In order to connect to our SSH machine at IP Address, 127.0.0.1, the application first asks us to confirm the RSA key fingerprint. In this case, we must answer, �yes� before continuing. Next, the application asks us to enter a password before granting us a command prompt. Finally, we execute our command uname �v to determine the kernel version running on our target. attacker$ ssh root@127.0.0.1 The authenticity of host '127.0.0.1 (127.0.0.1)' can't be established. RSA key fingerprint is 5b:bd:af:d6:0c:af:98:1c:1a:82:5c:fc:5c:39:a3:68. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '127.0.0.1' (RSA) to the list of known hosts. Password:************** Last login: Mon Oct 17 23:56:26 2011 from localhost attacker:! uname -v Darwin Kernel Version 11.2.0: Tue Aug 9 20:54:00 PDT 2011; root:xnu-1699.24.8!1/RELEASE_X86_64 In order to automate this interactive console, we will make use of a third party Python module named Pexpect (available to download at http://pexpect. sourceforge.net). Pexpect has the ability to interact with programs, watch for expected outputs, and then respond based on expected outputs. This makes it an excellent tool of choice for automating the process of brute forcing SSH user credentials. Building an SSH BotNet with Python 43 Examine the function connect(). This function takes a username, hostname, and password and returns an SSH connection resulting in an SSH spawned connection. Utilizing the pexpect library, it then waits for an expected output. Three possible expected outputs can occur�a timeout, a message indicating that the host has a new public key, or a password prompt. If a timeout occurs, then the session.expect() method returns to zero. The following selection statement notices this and prints an error message before returning. If the child.expect() method catches the ssh_newkey message, it returns a 1. This forces the function to send a message �yes� to accept the new key. Following this, the function waits for the password prompt before sending the SSH password. import pexpect PROMPT = ['# ', '>>> ', '> ', '\$ '] def send_command(child, cmd): child.sendline(cmd) child.expect(PROMPT) print child.before def connect(user, host, password): ssh_newkey = 'Are you sure you want to continue connecting' connStr = 'ssh ' + user + '@' + host child = pexpect.spawn(connStr) ret = child.expect([pexpect.TIMEOUT, ssh_newkey, \ '[P|p]assword:']) if ret == 0: print '[-] Error Connecting' return if ret == 1: child.sendline('yes') ret = child.expect([pexpect.TIMEOUT, \ '[P|p]assword:']) if ret == 0: print '[-] Error Connectingparser = optparse.OptionParser('usage %prog �H'+\ '<target host> -p <target port>') parser.add_option('-H', dest='tgtHost', type='string', \ help='specify target host') parser.add_option('-p', dest='tgtPort', type='int', \ help='specify target port') (options, args) = parser.parse_args() tgtHost = options.tgtHost tgtPort = options.tgtPort if (tgtHost == None) | (tgtPort == None): print parser.usage exit(0) Next, we will build two functions connScan and portScan. The portScan function takes the hostname and target ports as arguments. It will first attempt to resolve an IP address to a friendly hostname using the gethostbyname() function. Next, it will print the hostname (or IP address) and enumerate through each individual port attempting to connect using the connScan function. The connScan function will take two arguments: tgtHost and tgtPort and attempt to create a connection to the target host and port. If it is successful, connScan will print an open port message. If unsuccessful, it will print the closed port message. import optparse from socket import * def connScan(tgtHost, tgtPort): try: connSkt = socket(AF_INET, SOCK_STREAM) connSkt.connect((tgtHost, tgtPort)) print '[+]%d/tcp open'% tgtPort connSkt.close() except: print '[-]%d/tcp closed'% tgtPort def portScan(tgtHost, tgtPorts): try: Building a Port Scanner 35 tgtIP = gethostbyname(tgtHost) except: print "[-] Cannot resolve '%s': Unknown host"%tgtHost return try: tgtName = gethostbyaddr(tgtIP) print '\n[+] Scan Results for: ' + tgtName[0] except: print '\n[+] Scan Results for: ' + tgtIP setdefaulttimeout(1) for tgtPort in tgtPorts: print 'Scanning port ' + tgtPort connScan(tgtHost, int(tgtPort)) Application Banner Grabbing In order to grab the application banner from our target host, we must first insert additional code into the connScan function. After discovering an open port, we send a string of data to the port and wait for the response. Gathering this response might give us an indication of the application running on the target host and port. import optparse import socket from socket import * def connScan(tgtHost, tgtPort): try: connSkt = socket(AF_INET, SOCK_STREAM) connSkt.connect((tgtHost, tgtPort)) connSkt.send('ViolentPython\r\n') results = connSkt.recv(100) print '[+]%d/tcp open'% tgtPort print '[+] ' + str(results) connSkt.close() except: print '[-]%d/tcp closed'% tgtPort def portScan(tgtHost, tgtPorts): try: tgtIP = gethostbyname(tgtHost) except: 36 CHAPTER 2: Penetration Testing with Python print "[-] Cannot resolve '%s': Unknown host" %tgtHost return try: tgtName = gethostbyaddr(tgtIP) print '\n[+] Scan Results for: ' + tgtName[0] except: print '\n[+] Scan Results for: ' + tgtIP setdefaulttimeout(1) for tgtPort in tgtPorts: print 'Scanning port ' + tgtPort connScan(tgtHost, int(tgtPort)) def main(): parser = optparse.OptionParser("usage%prog "+\ "-H <target host> -p <target port>") parser.add_option('-H', dest='tgtHost', type='string', \ help='specify target host') parser.add_option('-p', dest='tgtPort', type='string', \ help='specify target port[s] separated by comma') (options, args) = parser.parse_args() tgtHost = options.tgtHost tgtPorts = str(options.tgtPort).split(', ') if (tgtHost == None) | (tgtPorts[0] == None): print '[-] You must specify a target host and port[s].' exit(0) portScan(tgtHost, tgtPorts) if __name__ == '__main__': main() For example, scanning a host with a FreeFloat FTP Server installed might reveal the following information in the banner grab: attacker$ python portscanner.py -H 192.168.1.37 -p 21, 22, 80 [+] Scan Results for: 192.168.1.37 Scanning port 21 [+] 21/tcp open [+] 220 FreeFloat Ftp Server (Version 1.00). In knowing that the server runs FreeFloat FTP (Version 1.00) this will prove to be useful for targeting our application as seen later. Building a Port Scanner 37 Threading the Scan Depending on the timeout variable for a socket, a scan of each socket can take several seconds. While this appears trivial, it quickly adds up if we are scanning multiple hosts or ports. Ideally, we would like to scan sockets simultaneously as opposed to sequentially. Enter Python threading. Threading provides a way to perform these kinds of executions simultaneously. To utilize this in our scan, we will modify the iteration loop in our portScan() function. Notice how we call the connScan function as a thread. Each thread created in the iteration will now appear to execute at the same time. for tgtPort in tgtPorts: t = Thread(target=connScan, args=(tgtHost, int(tgtPort))) t.start() While this provides us with a significant advantage in speed, it does present one disadvantage. Our function connScan() prints an output to the screen. If multiple threads print an output at the same time, it could appear garbled and out of order. In order to allow a function to have complete control of the screen, we will use a semaphore. A simple semaphore provides us a lock to prevent other threads from proceeding. Notice that prior to printing an output, we grabbed a hold of the lock using screenLock.acquire(). If open, the semaphore will grant us access to proceed and we will print to the screen. If locked, we will have to wait until the thread holding the semaphore releases the lock. By utilizing this semaphore, we now ensure only one thread can print to the screen at any given point in time. In our exception handling code, the keyword finally executes the following code before terminating the block. screenLock = Semaphore(value=1) def connScan(tgtHost, tgtPort): try: connSkt = socket(AF_INET, SOCK_STREAM) connSkt.connect((tgtHost, tgtPort)) connSkt.send('ViolentPython\r\n') results = connSkt.recv(100) screenLock.acquire() print '[+]%d/tcp open'% tgtPort print '[+] ' + str(results) except: screenLock.acquire() print '[-]%d/tcp closed'% tgtPort finally: 38 CHAPTER 2: Penetration Testing with Python screenLock.release() connSkt.close() Placing all other functions into the same script and adding some option parsing, we produce our final port scanner script. import optparse from socket import * from threading import * screenLock = Semaphore(value=1) def connScan(tgtHost, tgtPort): try: connSkt = socket(AF_INET, SOCK_STREAM) connSkt.connect((tgtHost, tgtPort)) connSkt.send('ViolentPython\r\n') results = connSkt.recv(100) screenLock.acquire() print '[+]%d/tcp open'% tgtPort print '[+] ' + str(results) except: screenLock.acquire() print '[-]%d/tcp closed'% tgtPort finally: screenLock.release() connSkt.close() def portScan(tgtHost, tgtPorts): try: tgtIP = gethostbyname(tgtHost) except: print "[-] Cannot resolve '%s': Unknown host"%tgtHost return try: tgtName = gethostbyaddr(tgtIP) print '\n[+] Scan Results for: ' + tgtName[0] except: print '\n[+] Scan Results for: ' + tgtIP setdefaulttimeout(1) for tgtPort in tgtPorts: t = Thread(target=connScan, args=(tgtHost, int(tgtPort))) t.start() Building a Port Scanner 39 def main(): parser = optparse.OptionParser('usage%prog '+\ '-H <target host> -p <target port>') parser.add_option('-H', dest='tgtHost', type='string', \ help='specify target host') parser.add_option('-p', dest='tgtPort', type='string', \ help='specify target port[s] separated by comma') (options, args) = parser.parse_args() tgtHost = options.tgtHost tgtPorts = str(options.tgtPort).split(', ') if (tgtHost == None) | (tgtPorts[0] == None): print parser.usage exit(0) portScan(tgtHost, tgtPorts) if __name__ == "__main__": main() Running the script against a target, we see it has an Xitami FTP server running on TCP port 21 and that TCP port 1720 is closed. attacker:!# python portScan.py -H 10.50.60.125 -p 21, 1720 [+] Scan Results for: 10.50.60.125 [+] 21/tcp open [+] 220- Welcome to this Xitami FTP server [-] 1720/tcp closed Integrating the Nmap Port Scanner Our preceding example provides a quick script for performing a TCP connect scan. This might prove limited as we may require the ability to perform additional scan types such as ACK, RST, FIN, or SYN-ACK scans provided by the Nmap toolkit (Vaskovich, 1997). The de facto standard for a port scanning toolkit, Nmap, delivers a rather extensive amount of functionality. This begs the question, why not just use Nmap? Enter the true beauty of Python. While Fyodor Vaskovich wrote Nmap and its associated scripts in the C and LUA programming languages, Nmap is able to be integrated rather nicely into Python. Nmap produces XML based output. Steve Milner and Brian Bustin wrote a Python library that parses this XML based output. This provides us with the ability to utilize the full functionality of Nmap within a Python script. Before starting, you must install Python-Nmap, available at http://xael.org/norman/ python/python-nmap/. Ensure you take into consideration the developer�s notes regarding the different versions of Python 3.x and Python 2.x. 40 CHAPTER 2: Penetration Testing with Python With Python-Nmap installed, we can now import Nmap into existing scripts and perform Nmap scans inline with your Python scripts. Creating a PortScanner() class object will allow us the capability to perform a scan on that object. The PortScanner class has a function scan() that takes a list of targets and ports as input and performs a basic Nmap scan. Additionally, we can now index the object by target hosts and ports and print the status of the port. The following sections will build upon this ability to locate and identify targets. import nmap import optparse def nmapScan(tgtHost, tgtPort): nmScan = nmap.PortScanner() nmScan.scan(tgtHost, tgtPort) state=nmScan[tgtHost]['tcp'][int(tgtPort)]['state'] print " [*] " + tgtHost + " tcp/"+tgtPort +" "+state def main(): parser = optparse.OptionParser('usage%prog '+\ '-H <target host> -p <target port>') parser.add_option('-H', dest='tgtHost', type='string', \ help='specify target host') parser.add_option('-p', dest='tgtPort', type='string', \ help='specify target port[s] separated by comma') (options, args) = parser.parse_args() tgtHost = options.tgtHost tgtPorts = str(options.tgtPort).split(', ') MORE INFORMATION� Other Types of Port Scans Consider a few other types of scans. While we lack the tools to craft packets with TCP options, we will cover this later in Chapter 5. At that time see if you can replicate some of these scan types in your port scanner. TCP SYN SCAN�Also known as a half-open scan, this type of scan initiates a TCP connection with a SYN packet and waits for a response. A reset packet indicates the port is closed while a SYN/ACK indicates the port is open. TCP NULL SCAN�A null scan sets the TCP flag header to zero. If a RST is received, it indicates the port is closed. TCP FIN SCAN�A TCP FIN Scan sends the FIN to tear down an active TCP connection and wait for a graceful termination. If a RST is received, it indicates the port is closed. TCP XMAS SCAN�An XMAS Scan sets the PSH, FIN, and URG TCP Flags. If a RST is received, it indicates the port is closed. Building an SSH BotNet with Python 41 if (tgtHost == None) | (tgtPorts[0] == None): print parser.usage exit(0) for tgtPort in tgtPorts: nmapScan(tgtHost, tgtPort) if __name__ == '__main__': main() Running our script that utilizes Nmap, we notice something interesting about TCP port 1720. The server or a firewall is actually filtering access to TCP port 1720. The port is not necessarily closed as we initially thought. Using a fullfledged scanner like Nmap instead of a single TCP connect scan we were able to discover the filter. attacker:!# python nmapScan.py -H 10.50.60.125 -p 21, 1720 [*] 10.50.60.125 tcp/21 open [*] 10.50.60.125 tcp/1720 filtered BUILDING AN SSH BOTNET WITH PYTHON Now that we have constructed a port scanner to find targets, we can begin the task of exploiting the vulnerabilities of each service. The Morris Worm includes forcing common usernames and passwords against the remote shell (RSH) service as one of its three attack vectors. In 1988, RSH provided an excellent (although not very secure) method for a system administrator to remotely connect to a machine and manage it by performing a series of terminal commands on the host. The Secure Shell (SSH) protocol has since replaced RSH by combining RSH with a public-key cryptographic scheme in order to secure the traffic. However, this does very little to stop the same attack vector by forcing out common user names and passwords. SSH Worms have proven to be very successful and common attack vectors. Take a look at the intrusion detection system (IDS) log from our very own www.violentpython.org for a recent SSH attack. Here, the attacker has attempted to connect to the machine using the accounts ucla, oxford, and matrix. These are interesting choices. Luckily for us, the IDS prevented further SSH login attempts from the attacking IP address after noticing its trend to forcibly produce the passwords. Received From: violentPython->/var/log/auth.log Rule: 5712 fired (level 10) -> "SSHD brute force trying to get access to the system." Portion of the log(s): Oct 13 23:30:30 violentPython sshd[10956]: Invalid user ucla from 67.228.3.58 42 CHAPTER 2: Penetration Testing with Python Oct 13 23:30:29 violentPython sshd[10954]: Invalid user ucla from 67.228.3.58 Oct 13 23:30:29 violentPython sshd[10952]: Invalid user oxford from 67.228.3.58 Oct 13 23:30:28 violentPython sshd[10950]: Invalid user oxford from 67.228.3.58 Oct 13 23:30:28 violentPython sshd[10948]: Invalid user oxford from 67.228.3.58 Oct 13 23:30:27 violentPython sshd[10946]: Invalid user matrix from 67.228.3.58 Oct 13 23:30:27 violentPython sshd[10944]: Invalid user matrix from 67.228.3.58 Interacting with SSH Through Pexpect Lets implement our own automated SSH Worm that brute forces user credentials against a target. Because SSH clients require user interaction, our script must be able to wait and match for an expected output before sending further input commands. Consider the following scenario. In order to connect to our SSH machine at IP Address, 127.0.0.1, the application first asks us to confirm the RSA key fingerprint. In this case, we must answer, �yes� before continuing. Next, the application asks us to enter a password before granting us a command prompt. Finally, we execute our command uname �v to determine the kernel version running on our target. attacker$ ssh root@127.0.0.1 The authenticity of host '127.0.0.1 (127.0.0.1)' can't be established. RSA key fingerprint is 5b:bd:af:d6:0c:af:98:1c:1a:82:5c:fc:5c:39:a3:68. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '127.0.0.1' (RSA) to the list of known hosts. Password:************** Last login: Mon Oct 17 23:56:26 2011 from localhost attacker:! uname -v Darwin Kernel Version 11.2.0: Tue Aug 9 20:54:00 PDT 2011; root:xnu-1699.24.8!1/RELEASE_X86_64 In order to automate this interactive console, we will make use of a third party Python module named Pexpect (available to download at http://pexpect. sourceforge.net). Pexpect has the ability to interact with programs, watch for expected outputs, and then respond based on expected outputs. This makes it an excellent tool of choice for automating the process of brute forcing SSH user credentials. Building an SSH BotNet with Python 43 Examine the function connect(). This function takes a username, hostname, and password and returns an SSH connection resulting in an SSH spawned connection. Utilizing the pexpect library, it then waits for an expected output. Three possible expected outputs can occur�a timeout, a message indicating that the host has a new public key, or a password prompt. If a timeout occurs, then the session.expect() method returns to zero. The following selection statement notices this and prints an error message before returning. If the child.expect() method catches the ssh_newkey message, it returns a 1. This forces the function to send a message �yes� to accept the new key. Following this, the function waits for the password prompt before sending the SSH password. import pexpect PROMPT = ['# ', '>>> ', '> ', '\$ '] def send_command(child, cmd): child.sendline(cmd) child.expect(PROMPT) print child.before def connect(user, host, password): ssh_newkey = 'Are you sure you want to continue connecting' connStr = 'ssh ' + user + '@' + host child = pexpect.spawn(connStr) ret = child.expect([pexpect.TIMEOUT, ssh_newkey, \ '[P|p]assword:']) if ret == 0: print '[-] Error Connecting' return if ret == 1: child.sendline('yes') ret = child.expect([pexpect.TIMEOUT, \ '[P|p]assword:']) if ret == 0: print '[-] Error Connecting