Authors: TJ O'Connor
During the last section, you might have found it repetitive to write almost the same exact code three times to check the three different IP addresses. Instead of writing the same thing three times, we might find it easier to use a for-loop to iterate through multiple elements. Consider, for example: if we wanted to iterate through the entire /24 subnet of IP addresses for 192.168.95.1 through 192.168.95.254, using a for-loop with the range from 1 to 255 allows us to print out the entire subnet.
>>> for x in range(1,255):
... print “192.168.95.”+str(x)
...
192.168.95.1
192.168.95.2
192.168.95.3
192.168.95.4
192.168.95.5
192.168.95.6
...
192.168.95.253
192.168.95.254
Similarly, we may want to iterate through a known list of ports to check for vulnerabilities. Instead of iterating through a range of numbers, we can iterate through an entire list of elements.
>>> portList = [21,22,25,80,110]
>>> for port in portList:
... print port
...
21
22
25
80
110
Nesting our two for-loops, we can now print out each IP address and the ports for each address.
>>> for x in range(1,255):
... for port in portList:
... print “[+] Checking 192.168.95.”\
+str(x)+”: “+str(port)
...
[+] Checking 192.168.95.1:21
[+] Checking 192.168.95.1:22
[+] Checking 192.168.95.1:25
[+] Checking 192.168.95.1:80
[+] Checking 192.168.95.1:110
[+] Checking 192.168.95.2:21
[+] Checking 192.168.95.2:22
[+] Checking 192.168.95.2:25
[+] Checking 192.168.95.2:80
[+] Checking 192.168.95.2:110
<... SNIPPED ...>
With the ability to iterate through IP addresses and ports, we will update our vulnerability-checking script. Now our script will test all 254 IP addresses on the 192.168.95.0/24 subnet with the ports offering telnet, SSH, smtp, http, imap, and https services.
import socket
def retBanner(ip, port):
try:
socket.setdefaulttimeout(2)
s = socket.socket()
s.connect((ip, port))
banner = s.recv(1024)
return banner
except:
return
def checkVulns(banner):
if ‘FreeFloat Ftp Server (Version 1.00)’ in banner:
print ‘[+] FreeFloat FTP Server is vulnerable.’
elif ‘3Com 3CDaemon FTP Server Version 2.0’ in banner:
print ‘[+] 3CDaemon FTP Server is vulnerable.’
elif ‘Ability Server 2.34’ in banner:
print ‘[+] Ability FTP Server is vulnerable.’
elif ‘Sami FTP Server 2.0.2’ in banner:
print ‘[+] Sami FTP Server is vulnerable.’
else:
print ‘[-] FTP Server is not vulnerable.’
return
def main():
portList = [21,22,25,80,110,443]
for x in range(1, 255):
ip = ‘192.168.95.’ + str(x)
for port in portList:
banner = retBanner(ip, port)
if banner:
print ‘[+] ‘ + ip + ‘: ‘ + banner
checkVulns(banner)
if __name__ == ‘__main__’:
main()
While our script has an IF statement that checks a few vulnerable banners, it would be nice to occasionally add a new list of vulnerable banners. For this example, let’s assume we have a text file called vuln_banners.txt. Each line in this file lists a specific service version with a previous vulnerability. Instead of constructing a huge IF statement, let’s read in this text file and use it to make decisions if our banner is vulnerable.
programmer$ cat vuln_banners.txt
3Com 3CDaemon FTP Server Version 2.0
Ability Server 2.34
CCProxy Telnet Service Ready
ESMTP TABS Mail Server for Windows NT
FreeFloat Ftp Server (Version 1.00)
IMAP4rev1 MDaemon 9.6.4 ready
MailEnable Service, Version: 0-1.54
NetDecision-HTTP-Server 1.0
PSO Proxy 0.9
SAMBAR
Sami FTP Server 2.0.2
Spipe 1.0
TelSrv 1.5
WDaemon 6.8.5
WinGate 6.1.1
Xitami
YahooPOPs! Simple Mail Transfer Service Ready
We will place our updated code in the checkVulns function. Here, we will open the text file in read-only mode (‘r’). We iterate through each line in the file using the method .readlines(). For each line, we compare it against our banner. Notice that we must strip out the carriage return from each line using the method .strip(‘\r’). If we detect a match, we print the vulnerable service banner.
def checkVulns(banner):
f = open(“vuln_banners.txt”,’r’)
for line in f.readlines():
if line.strip(‘\n’) in banner:
print “[+] Server is vulnerable: “+banner.strip(‘\n’)
The built-in sys module provides access to objects used or maintained by the Python interpreter. This includes flags, version, max sizes of integers, available modules, path hooks, location of standard error/in/out, and command line arguments called by the interpreter. You can find more information on the Python online module documents available from
http://docs.python.org/library/sys
. Interacting with the sys module can prove very helpful in creating Python scripts. We may, for example, want to parse command line arguments at runtime. Consider our vulnerability scanner: what if we wanted to pass the name of a text file as a command line argument? The list sys.argv contains all the command line arguments. The first index sys.argv[0] contains the name of
the interpreter Python script. The remaining items in the list contain all the following command line arguments. Thus, if we are only passing one additional argument, sys.argv should contain two items.
import sys
if len(sys.argv)==2:
filename = sys.argv[1]
print “[+] Reading Vulnerabilities From: “+filename
Running our code snippet, we see that the code successfully parses the command line argument and prints it to the screen. Take the time to examine the entire sys module for the wealth of capabilities it provides to the programmer.
programmer$ python vuln-scanner.py vuln-banners.txt
[+] Reading Vulnerabilities From: vuln-banners.txt
The built-in OS module provides a wealth of OS routines for Mac, NT, or Posix operating systems. This module allows the program to independently interact with the OS environment, file-system, user database, and permissions. Consider, for example, the last section, where the user passed the name of a text file as a command line argument. It might prove valuable to check to see if that file exists and the current user has read permissions to that file. If either condition fails, it would be useful to display an appropriate error message to the user.
import sys
import os
if len(sys.argv) == 2:
filename = sys.argv[1]
if not os.path.isfile(filename):
print ‘[-] ‘ + filename + ‘ does not exist.’
exit(0)
if not os.access(filename, os.R_OK):
print ‘[-] ‘ + filename + ‘ access denied.’
exit(0)
print ‘[+] Reading Vulnerabilities From: ‘ + filename
To verify our code, we initially try to read a file that does not exist, which causes our script to print an error. Next, we create the specific filename and
successfully read it. Finally, we restrict permission and see that our script correctly prints the access-denied message.
programmer$ python test.py vuln-banners.txt
[-] vuln-banners.txt does not exist.
programmer$ touch vuln-banners.txt
programmer$ python test.py vuln-banners.txt
[+] Reading Vulnerabilities From: vuln-banners.txt
programmer$ chmod 000 vuln-banners.txt
programmer$ python test.py vuln-banners.txt
[-] vuln-banners.txt access denied.
We can now reassemble all the various pieces and parts of our Python vulnerability-scanning script. Do not worry if it appears pseudo-complete, lacking the ability to use threads of execution or better command line option parsing. We will continue to build upon this script in the following chapter.
Import socket
import os
import sys
def retBanner(ip, port):
try:
socket.setdefaulttimeout(2)
s = socket.socket()
s.connect((ip, port))
banner = s.recv(1024)
return banner
except:
return
def checkVulns(banner, filename):
f = open(filename, ‘r’)
for line in f.readlines():
if line.strip(‘\n’) in banner:
print ‘[+] Server is vulnerable: ‘ +\
banner.strip(‘\n’)
def main():
if len(sys.argv) == 2:
filename = sys.argv[1]
if not os.path.isfile(filename):
print ‘[-] ‘ + filename +\
‘ does not exist.’
exit(0)
if not os.access(filename, os.R_OK):
print ‘[-] ‘ + filename +\
‘ access denied.’
exit(0)
else:
print ‘[-] Usage: ‘ + str(sys.argv[0]) +\
‘
exit(0)
portList = [21,22,25,80,110,443]
for x in range(147, 150):
ip = ‘192.168.95.’ + str(x)
for port in portList:
banner = retBanner(ip, port)
if banner:
print ‘[+] ‘ + ip + ‘: ‘ + banner
checkVulns(banner, filename)
if __name__ == ‘__main__’:
main()
With an understanding how to build Python scripts, let us begin writing our first two programs. As we move forward, we will describe a few anecdotal stories that emphasize the need for our scripts.
A system administrator at Lawrence Berkley National Labs, Clifford Stoll, documented his personal hunt for a hacker (and KGB informant) who broke into various United States national research laboratories, army bases, defense contractors, and academic institutions in
The Cuckoo’s Egg: Tracking a Spy Through the Maze of Computer Espionage
(
Stoll, 1989
). He also published a May 1988 article in
Communications of the ACM
describing the in-depth technical details of the attack and hunt (
Stoll, 1988
).
Fascinated by the attacker’s methodology and actions, Stoll connected a printer to a compromised server and logged every keystroke the attacker made. During one recording, Stoll noticed something interesting (at least in 1988).
Almost immediately after compromising a victim, the attacker downloaded the encrypted password file. What use was this to the attacker? After all, the victim systems encrypted the user passwords using the UNIX crypt algorithm. However, within a week of stealing the encrypted password files, Stoll saw the attacker log on with the stolen accounts. Confronting some of the victim users, he learned they had used common words from the dictionary as passwords (
Stoll, 1989
).
Upon learning this, Stoll realized that the hacker had used a dictionary attack to decrypt the encrypted passwords. The hacker enumerated through all the words in a dictionary and encrypted them using the Unix Crypt() function. After encrypting each password, the hacker compared it with the stolen encrypted password. The match translated to a successful password crack.
Consider the following encrypted password file. The victim used a plaintext password
egg
and salt equal to the first two bytes or
HX
. The UNIX Crypt function calculates the encrypted password with
crypt(‘egg’,’HX’) = HX9LLTdc/jiDE.
attacker$ cat /etc/passwd
victim: HX9LLTdc/jiDE: 503:100:Iama Victim:/home/victim:/bin/sh
root: DFNFxgW7C05fo: 504:100: Markus Hess:/root:/bin/bash
Let’s use this encrypted password file as an opportunity to write our first Python script, a UNIX password cracker.
The real strength of the Python programming language lies in the wide array of standard and third-party libraries. To write our UNIX password cracker, we will need to use the crypt() algorithm that hashes UNIX passwords. Firing up the Python interpreter, we see that the crypt library already exists in the Python standard library. To calculate an encrypted UNIX password hash, we simply call the function crypt.crypt() and pass it the password and salt as parameters. This function returns the hashed password as a string.
Programmer$ python
>>> help(‘crypt’)
Help on module crypt:
NAME
crypt
FILE
/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-dynload/crypt.so
MODULE DOCS
http://docs.python.org/library/crypt
FUNCTIONS
crypt(...)
crypt(word, salt) -> string
word will usually be a user’s password. salt is a 2-character string
which will be used to select one of 4096 variations of DES. The
characters in salt must be either “.”, “/”, or an alphanumeric
character. Returns the hashed password as a string, which will be
composed of characters from the same alphabet as the salt.
Let’s quickly try hashing a password using the crypt() function. After importing the library, we pass the password “egg” and the salt “HX” to the function. The function returns the hashed password value “HX9LLTdc/jiDE” as a string. Success! Now we can write a program to iterate through an entire dictionary, trying each word with the custom salt for the hashed password.
programmer$ python
>>> import crypt
>>> crypt.crypt(“egg”,”HX”)
‘HX9LLTdc/jiDE’
To write our program, we will create two functions-main and testpass. It proves a good programming practice to separate your program into separate functions, each with a specific purpose. In the end, this allows us to reuse code and makes the program easier to read. Our main function opens the encrypted password file “passwords.txt” and reads the contents of each line in the password file. For each line, it splits out the username and the hashed password. For each individual hashed password, the main function calls the testPass() function that tests passwords against a dictionary file.
This function, testPass(), takes the encrypted password as a parameter and returns either after finding the password or exhausting the words in the dictionary. Notice that the function first strips out the salt from the first two characters of the encrypted password hash. Next, it opens the dictionary and iterates through each word in the dictionary, creating an encrypted password hash from the dictionary word and the salt. If the result matches our encrypted password hash, the function prints a message indicating the found password and returns. Otherwise, it continues to test every word in the dictionary.
import crypt
def testPass(cryptPass):
salt = cryptPass[0:2]
dictFile = open(‘dictionary.txt’,’r’)
for word in dictFile.readlines():
word = word.strip(‘\n’)
cryptWord = crypt.crypt(word,salt)
if (cryptWord == cryptPass):
print “[+] Found Password: “+word+”\n”
return
print “[-] Password Not Found.\n”
return
def main():
passFile = open(‘passwords.txt’)
for line in passFile.readlines():
if “:” in line:
user = line.split(‘:’)[0]
cryptPass = line.split(‘:’)[1].strip(‘ ‘)
print “[∗] Cracking Password For: “+user
testPass(cryptPass)
if __name__ == “__main__”:
main()
Running our first program, we see that it successfully cracks the password for victim but does not crack the password for root. Thus, we know the system administrator (root) must be using a word not in our dictionary. No need to worry, we’ll cover several other ways in this book to gain root access.
programmer$ python crack.py
[∗] Cracking Password For: victim
[+] Found Password: egg
[∗] Cracking Password For: root
[-] Password Not Found.
On modern ∗Nix based operating systems, the /etc/shadow file stores the hashed password and provides the ability to use more secure hashing algorithms. The following example uses the SHA-512 hashing algorithm. SHA-512 functionality is provided by the Python hashlib library. Can you update the script to crack SHA-512 hashes?
cat /etc/shadow | grep root
root:$6$ms32yIGN$NyXj0YofkK14MpRwFHvXQW0yvUid.slJtgxHE2EuQqgD74S/GaGGs5VCnqeC.bS0MzTf/EFS3uspQMNeepIAc.:15503:0:99999:7:::
Announcing his Cyber Fast Track program at ShmooCon 2012, Peiter “Mudge” Zatko, the legendary l0pht hacker turned DARPA employee, explained that there are really no offensive or defensive tools-instead there are simply tools (
Zatko, 2012
). Throughout this book, you may initially find several of the example scripts somewhat offensive in nature. For example, take our last program that cracked passwords on Unix systems. An adversary
could
use the tool to gain unauthorized access to a system; however, could a programmer use this for good as well as evil? Certainly—let’s expand.
Fast-forward nineteen years from Clifford Stoll’s discovery of the dictionary attack. In early 2007, the Brownsville, TX Fire Department received an anonymous tip that fifty-year-old John Craig Zimmerman browsed child pornography using department resources (
Floyd, 2007
). Almost immediately, the Brownsville Fire Department granted Brownsville police investigators access to Zimmerman’s work computer and external hard drive (
Floyd, 2007
). The police department brought in city programmer Albert Castillo to search the contents of Zimmerman’s computer (
McCullagh, 2008
). Castillo’s initial investigation found several adult pornographic images but no child pornography.
Continuing to browse through the files, Castillo found some suspect files, including a password-protected ZIP file titled “Cindy 5.” Relying on the technique invented nearly two decades earlier, Castillo used a dictionary attack to decrypt the contents of the password-protected file. The resulting decrypted files showed a partially naked minor (
McCullagh, 2008
). With this information, a judge granted investigators a warrant to search Zimmerman’s home, where they discovered several additional pornographic images of children (
McCullagh, 2008
). On April 3, 2007, a federal grand jury returned an indictment, charging John Craig Zimmerman with four counts of possession and production of child pornography (
Floyd, 2007
).
Let’s use the technique of brute-forcing a password learned in the last example program but apply it to zip files. We will also use this example to expand upon some fundamental concepts of building our programmers.
Let’s begin writing our zip-file password cracker by examining the zipfile library. Opening the Python interpreter, we issue the command help(‘zipfile’) to learn more about the library and see the class ZipFile with a method extractall(). This class and method will prove useful in writing our program to crack password-protected zip files. Note how the method extractall() has an optional parameter to specify a password.
programmer$ python
Python 2.7.1 (r271:86832, Jun 16 2011, 16:59:05)
Type “help”, “copyright”, “credits” or “license” for more information.
>>> help(‘zipfile’)
<..SNIPPED..>
class ZipFile
| Class with methods to open, read, write, close, list zip files.
|
| z = ZipFile(file, mode=”r”, compression=ZIP_STORED, allowZip64=False)
<..SNIPPED..>
| extractall(self, path=None, members=None, pwd=None)
| Extract all members from the archive to the current working
| directory. ‘path’ specifies a different directory to extract to.
| ‘members’ is optional and must be a subset of the list returned
Let’s write a quick script to test the use of the zipfile library. After importing the library, we instantiate a new ZipFile class by specifying the filename of the password-protected zip file. To extract the zip file, we utilize the extractall() method and specify the optional parameter for the password.
import zipfile
zFile = zipfile.ZipFile(“evil.zip”)
zFile.extractall(pwd=”secret”)
Next, we execute our script to ensure it works properly. Notice that prior to execution, only the script and the zip file exist in our current working directory. We execute our script, which extracts the contents of evil.zip to a newly created directory called evil/. This directory contains the files from the previously password-protected zip file.
programmer$ ls
evil.zip unzip.py
programmer$ python unzip.py
programmer$ ls
evil.zip unzip.py evil
programmer$ cd evil/
programmer$ ls
note_to_adam.txt apple.bmp
However, what happens if we execute the script with an incorrect password? Let’s add some exception handling to catch and display the error message from the script.
import zipfile
zFile = zipfile.ZipFile(“evil.zip”)
try:
zFile.extractall(pwd=”oranges”)
except Exception, e:
print e
Executing our script with an incorrect password, we see that it prints an error message, indicating that the user specified an incorrect password to decrypt the contents of the password-protected zip file.
programmer$ python unzip.py
(‘Bad password for file’,
We can use the fact that an incorrect password throws an exception to test our zip file against a dictionary file. After instantiating a ZipFile class, we open a dictionary file and iterate through and test each word in the dictionary. If the method extractall() executes without error, we print a message indicating the working password. However, if extractall() throws a bad password exception, we ignore the exception and continue trying passwords in the dictionary.
import zipfile
zFile = zipfile.ZipFile(‘evil.zip’)
passFile = open(‘dictionary.txt’)
for line in passFile.readlines():
password = line.strip(‘\n’)
try:
zFile.extractall(pwd=password)
print ‘[+] Password = ‘ + password + ‘\n’
exit(0)
except Exception, e:
pass
Executing our script, we see that it correctly identifies the password for the password-protected zip file.
programmer$ python unzip.py
[+] Password = secret
Let’s clean up our code a little bit at this point. Instead of having a linear program, we will modularize our script with functions.
import zipfile
def extractFile(zFile, password):
try:
zFile.extractall(pwd=password)
return password
except:
return
def main():
zFile = zipfile.ZipFile(‘evil.zip’)
passFile = open(‘dictionary.txt’)
for line in passFile.readlines():
password = line.strip(‘\n’)
guess = extractFile(zFile, password)
if guess:
print ‘[+] Password = ‘ + password + ‘\n’
exit(0)
if __name__ == ‘__main__’:
main()
With our program modularized into separate functions, we can now increase our performance. Instead of trying each word in the dictionary one at a time, we will utilize threads of execution to allow simultaneous testing of multiple passwords. For each word in the dictionary, we will spawn a new thread of execution.
import zipfile
from threading import Thread
def extractFile(zFile, password):
try:
zFile.extractall(pwd=password)
print ‘[+] Found password ‘ + password + ‘\n’
except:
pass
def main():
zFile = zipfile.ZipFile(‘evil.zip’)
passFile = open(‘dictionary.txt’)
for line in passFile.readlines():
password = line.strip(‘\n’)
t = Thread(target=extractFile, args=(zFile, password))
t.start()
if __name__ == ‘__main__’:
main()
Now let’s modify our script to allow the user to specify the name of the zip file to crack and the name of the dictionary file. To do this, we will import the optparse library. We will describe this library better in the next chapter. For the purposes of our script here, we only need to know that it parses flags and optional parameters following our script. For our zip-file-cracker script, we will add two mandatory flags—zip file name and dictionary name.
import zipfile
import optparse
from threading import Thread
def extractFile(zFile, password):
try:
zFile.extractall(pwd=password)
print ‘[+] Found password ‘ + password + ‘\n’
except:
pass
def main():
parser = optparse.OptionParser(“usage%prog “+\
“-f
parser.add_option(‘-f’, dest=’zname’, type=’string’,\
help=’specify zip file’)
parser.add_option(‘-d’, dest=’dname’, type=’string’,\
help=’specify dictionary file’)
(options, args) = parser.parse_args()
if (options.zname == None) | (options.dname == None):
print parser.usage
exit(0)
else:
zname = options.zname
dname = options.dname
zFile = zipfile.ZipFile(zname)
passFile = open(dname)
for line in passFile.readlines():
password = line.strip(‘\n’)
t = Thread(target=extractFile, args=(zFile, password))
t.start()
if __name__ == ‘__main__’:
main()
Finally, we test our completed password-protected zip-file-cracker script to ensure it works. Success with a thirty-five-line script!
programmer$ python unzip.py -f evil.zip -d dictionary.txt
[+] Found password secret