Authors: TJ O'Connor
In the following pages, we will tackle the idea of variables, data types, strings, complex data structures, networking, selection, iteration, file handling, exception handling, and interoperability with the operating system. To illustrate this, we will build a simple vulnerability scanner that connects to a TCP socket, reads the banner from a service, and compares that banner against known vulnerable service versions. As an experienced programmer, you may find some
of the initial code examples very ugly in design. In fact, hopefully you do. As we continue to develop our script in this section, the script will hopefully grow into an elegant design you can appreciate. Let’s begin by starting with the bedrock of any programming language—variables.
In Python, a variable points to data stored in a memory location. This memory location can store different values such as integers, real numbers, Booleans, strings, or more complex data such as lists or dictionaries. In the following code, we define a variable
port
that stores an integer and
banner
that stores a string. To combine the two variables together into one string, we must explicitly cast the port as a string using the str() function.
>>> port = 21
>>> banner = “FreeFloat FTP Server”
>>> print “[+] Checking for “+banner+” on port “+str(port)
[+] Checking for FreeFloat FTP Server on port 21
Python reserves memory space for variables when the programmer declares them. The programmer does not have to explicitly declare the type of variable; rather, the Python interpreter decides the type of the variable and how much space in the memory to reserve. Considering the following example, we declare a string, an integer, a list, and a Boolean, and the interpreter correctly automatically types each variable.
>>> banner = “FreeFloat FTP Server” # A string
>>> type(banner)
>>> port = 21 # An integer
>>> type(port)
>>> portList=[21,22,80,110] # A list
>>> type(portList)
>>> portOpen = True # A boolean
>>> type(portOpen)
The Python string module provides a very robust series of methods for strings. Read the Python documentation at
http://docs.python.org/library/string
.html for the entire list of available methods. Let’s examine a few useful methods.
Consider the use of the following methods: upper(), lower(), replace(), and find(). Upper() converts a string to its uppercase variant. Lower() converts a string to its lowercase variant. Replace(old,new) replaces the old occurrence of the substring old with the substring new. Find() reports the offset where the first occurrence of the substring occurs.
>>> banner = “FreeFloat FTP Server”
>>> print banner.upper()
FREEFLOAT FTP SERVER
>>> print banner.lower()
freefloat ftp server
>>> print banner.replace(‘FreeFloat’,’Ability’)
Ability FTP Server
>>> print banner.find(‘FTP’)
10
The list data structure in Python provides an excellent method for storing arrays of objects in Python. A programmer can construct lists of any data type. Furthermore, built-in methods exist for performing actions such as appending, inserting, removing, popping, indexing, counting, sorting, and reversing lists. Consider the following example: a programmer can construct a list by appending items using the append() method, print the items, and then sort them before printing again. The programmer can find the index of a particular item (the integer 80 in this example). Furthermore, specific items can be removed (the integer 443 in this example).
>>> portList = []
>>> portList.append(21)
>>> portList.append(80)
>>> portList.append(443)
>>> portList.append(25)
>>> print portList
[21, 80, 443, 25]
>>> portList.sort()
>>> print portList
[21, 25, 80, 443]
>>> pos = portList.index(80)
>>> print “[+] There are “+str(pos)+” ports to scan before 80.”
[+] There are 2 ports to scan before 80.
>>> portList.remove(443)
>>> print portList
[21, 25, 80]
>>> cnt = len(portList)
>>> print “[+] Scanning “+str(cnt)+” Total Ports.”
[+] Scanning 3 Total Ports.
The Python dictionary data structure provides a hash table that can store any number of Python objects. The dictionary consists of pairs of items that contain a key and value. Let’s continue with our example of a vulnerability scanner to illustrate a Python dictionary. When scanning specific TCP ports, it may prove useful to have a dictionary that contains the common service names for each port. Creating a dictionary, we can lookup a key like
ftp
and return the associated value
21
for that port.
When constructing a dictionary, each key is separated from its value by a colon, and we separate items by commas. Notice that the method .keys() will return a list of all keys in the dictionary and that the method .items() will return an entire list of items in the dictionary. Next, we verify that the dictionary contains a specific key (ftp). Referencing this key returns the value 21.
>>> services = {’ftp’:21,’ssh’:22,’smtp’:25,’http’:80}
>>> services.keys()
[’ftp’, ‘smtp’, ‘ssh’, ‘http’]
>>> services.items()
[(‘ftp’, 21), (‘smtp’, 25), (‘ssh’, 22), (‘http’, 80)]
>>> services.has_key(‘ftp’)
True
>>> services[’ftp’]
21
>>> print “[+] Found vuln with FTP on port “+str(services[’ftp’])
[+] Found vuln with FTP on port 21
The socket module provides a library for making network connections using Python. Let’s quickly write a banner-grabbing script. Our script will print the banner after connecting to a specific IP address and TCP port. After importing the socket module, we instantiate a new variable s from the class socket class. Next, we use the connect() method to make a network connection to the IP address and port. Once successfully connected, we can read and write from the socket.
The recv(1024) method will read the next 1024 bytes on the socket. We store the result of this method in a variable and then print the results to the server.
>>> import socket
>>> socket.setdefaulttimeout(2)
>>> s = socket.socket()
>>> s.connect((“192.168.95.148”,21))
>>> ans = s.recv(1024)
>>> print ans
220 FreeFloat Ftp Server (Version 1.00).
Like most programming languages, Python provides a method for conditional select statements. The IF statement evaluates a logical expression in order to make a decision based on the result of the evaluation. Continuing with our banner-grabbing script, we would like to know if the specific FTP server is vulnerable to attack. To do this, we will compare our results against some known vulnerable FTP server versions.
>>> import socket
>>> socket.setdefaulttimeout(2)
>>> s = socket.socket()
>>> s.connect((“192.168.95.148”,21))
>>> ans = s.recv(1024)
>>> if (“FreeFloat Ftp Server (Version 1.00)” in ans):
... 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.”
...
[+] FreeFloat FTP Server is vulnerable.”
Even when a programmer writes a syntactically correct program, the program may still error at runtime or execution. Consider the classic runtime error—division by zero. Because zero cannot divide a number, the Python interpreter
displays a message informing the programmer of the error message. This error ceases program execution.
>>> print 1337/0
Traceback (most recent call last):
File “
ZeroDivisionError: integer division or modulo by zero
What happens if we just wanted to handle the error within the context of the running program or script? The Python language provides exception-handling capability to do just this. Let’s update the previous example. We use try/except statements to provide exception handling. Now, the program tries to execute the division by zero. When the error occurs, our exception handling catches the error and prints a message to the screen.
>>> try:
... print “[+] 1337/0 = “+str(1337/0)
... except:
... print “[-] Error. “
...
[-] Error
>>>
Unfortunately, this gives us very little information about the exact exception that caused the error. It might be useful to provide the user with an error message about the specific error that occurred. To do this, we will store the exception in a variable e to print the exception, then explicitly cast the variable e as a string.
>>> try:
... print “[+] 1337/0 = “+str(1337/0)
... except Exception, e:
... print “[-] Error = “+str(e)
...
[-] Error = integer division or modulo by zero
>>>
Let’s now use exception handling to update our banner-grabbing script. We will wrap the network connection code with exception handling. Next, we try to connect to a machine that is not running a FTP Server on TCP port 21. If we wait for the connection timeout, we see a message indicating the network connection operation timed out. Our program can now continue.
>>> import socket
>>> socket.setdefaulttimeout(2)
>>> s = socket.socket()
>>> try:
... s.connect((“192.168.95.149”,21))
... except Exception, e:
... print “[-] Error = “+str(e)
...
[-] Error = Operation timed out
Let us provide you one caveat about exception handling in this book. In order to cleanly illustrate the wide variety of concepts in the following pages, we have put minimal exception handling into the scripts in this book. Feel free to update the scripts included on the companion website to add more robust exception handling.
In Python, functions provide organized blocks of reusable code. Typically, this allows a programmer to write a block of code to perform a single, related action. While Python provides many built-in functions, a programmer can create user-defined functions. The keyword def() begins a function. The programmer can place any variables inside the parenthesis. These variables are then passed by reference, meaning that any changes to these variables inside the function will affect their value from the calling function. Continuing with the previous FTP vulnerability-scanning example, let’s create a function to perform just the action of connecting to the FTP server and returning the banner.
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 main():
ip1 = ‘192.168.95.148’
ip2 = ‘192.168.95.149’
port = 21
banner1 = retBanner(ip1, port)
if banner1:
print ‘[+] ‘ + ip1 + ‘: ‘ + banner1
banner2 = retBanner(ip2, port)
if banner2:
print ‘[+] ‘ + ip2 + ‘: ‘ + banner2
if __name__ == ‘__main__’:
main()
After returning the banner, our script needs to check this banner against some known vulnerable programs. This also reflects a single, related function. The function checkVulns() takes the variable banner as a parameter and then uses it to make a determination of the vulnerability of the server.
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():
ip1 = ‘192.168.95.148’
ip2 = ‘192.168.95.149’
ip3 = ‘192.168.95.150’
port = 21
banner1 = retBanner(ip1, port)
if banner1:
print ‘[+] ‘ + ip1 + ‘: ‘ + banner1.strip(‘\n’)
checkVulns(banner1)
banner2 = retBanner(ip2, port)
if banner2:
print ‘[+] ‘ + ip2 + ‘: ‘ + banner2.strip(‘\n’)
checkVulns(banner2)
banner3 = retBanner(ip3, port)
if banner3:
print ‘[+] ‘ + ip3 + ‘: ‘ + banner3.strip(‘\n’)
checkVulns(banner3)
if __name__ == ‘__main__’:
main()