Learn Python (Part 3)

Learn Python (Part 3)

Python Basics(Lists)

Read part 1 here :- Learn Python (Part 1)
R
ead part 2 here :- Learn Python (Part 2)

If we want to watch a site to make sure it stays up during our test, we can use the watch command. To update every five seconds, we will specify the en argument with 5 as a value. Examine Figure as an example.When we use ARGV, we must give the arguments in a specific order and we have to handle all the error checking and assignment of values to our variables. The optparse module provides a class called OptionParser that helps us with this problem. Let’s investigate OptionParser by modifying webCheck.py:

#!/usr/bin/python
import httplib, sys
from optparse import OptionParser
usageString = “Usage: %prog [options] hostname”
parser = OptionParser(usage=usageString)
parser.add_option(“-p”, “–port”, dest=”port”, metavar=”PORT”,
default=80, type=”int”, help=”Port to connect to”)
(opts,args) = parser.parse_args()
if len(args) < 1:
parser.error(“Hostname is required”)
host = args[0]

Untitled

From this point on, the rest of the script is the same. We begin by importing only the OptionParser class from our optparse module. We create a usage statement we can give to our parser, and then we define the parser and pass the statement as a usage option.We could pass this directly to the parser without making it a variable first, but using a variable is both easier to read and allows us to reuse the usage statement elsewhere if we like. OptionParser will display our usage statement if the script is run with an eh flag. Since most Web services run on port 80, we might not always want to have to type a port when we use our script. So we add an option that allows us to specify a port on the command line.We tell the parser that both ep and eport can be used to specify the port. Metavar tells us what arguments the ep or eport flag requires, while the help flag defines the help text for the detailed help display. The default value is where we say that port 80 is most common, and dest is the variable name in which we will store a different value if we don’t use the default. Note the indentation after this line. The indentation tells the interpreter that we are continuing our previous statement. This allows us to split up our lines in a way that makes our code
more readable. Now that our options are set up, we call the parse opts method of our parser class. The output sets two variables: opts and args. Opts is set via the options we specify, and args is anything else that will be passed on the command line, in this case, our host name. From here, everything else works the same with the exception
of where our host and port values come from. Our host is the first item in the args list, and our port we can specify directly from the opts object.
Let’s test out our new program. Take a look at Figure

Running webCheck.py Using OptionParser

Lists

Let’s say we need to convert a Classless Inter-Domain Routing (CIDR)-formatted IP address into an IP range and netmask. CIDR format is a shorter way to express information about an IP address. Instead of listing out the full network information, only the IP address and the number of bits in the netmask are present. There are a few ways to do this.We can calculate it by hand, use a subnet calculator, or write a script. Since you’re reading a scripting book, we should probably use a script. This will also give us an opportunity to explore lists. Lists are Python’s version of arrays. They are objects with their own methods. Let’s make something like this:

[email protected]:~# ./subcalc.py 192.168.1.1/24

First, we’ll want to split the input (192.168.1.1/24) into the CIDR and the IP address for individual processing.
addrString,cidrString = sys.argv[1].split(‘/’) The string split method always returns a list. In this case, our list will have two values: the IP address (which we put into the addrString variable) and the CIDR notation (which we put into the cidrString variable. We tell split to use the slash to determine where to break the string into our list elements. Now we’ll want to do something similar with our IP address in order to parse each octet individually:

addr = addrString.split(‘.’)

This time we’re using a period as a delimiter and using addr to store our list of octets. But there’s one problem. We have a bunch of strings. We’re going to have to do math with this to calculate things such as the broadcast parameters, the netmask, and, ultimately, our IPs. So let’s convert our CIDR into an integer.

NOTE
For a refresher on subnetting and CIDR addresses, visit Appendix: Subnetting and CIDR
Addresses where we walk through how all of this works at the network layer!

cidr = int(cidrString)

Now we can start determining the netmask. Let’s start with 0.0.0.0 and add our way up using the CIDR:

mask = [0,0,0,0]
for i in range(cidr):
mask[i/8] = mask[i/8] + (1 << (7 – i % 8))

Whoa, Nelly! What’s going on in this channel? To determine our network, we need to determine how many bits are in that mask. This is the CIDR. We start at the left-hand side (the 192 side of the IP) and set bits,starting at the most significant bit and moving right to the least significant bit. A CIDR mask of 1 would give us a netmask value of 128.0.0.0, and a CIDR value of 24 should give us a netmask value of 255.255.255.0. In this script, we’re going to set the bits from left to right using binary bit shifting in the range defined by our CIDR. We use a for loop to iterate through this range and do the math. That math, in words, is: Take the mod of the current iterator and eight. Subtract it from seven. Bit-shift one that many places. Then divide the value of our iterator by eight to determine which octet we are manipulating, and add that list value to the result. Take this result and put it in the string in the location defined by the current bit divided by eight. Then move on to doing the same thing with two. This is a pretty pedantic way to get the result. But this is a learning exercise, and we’re talking about lists. Now that we have our netmask, we can calculate the network IP. We’ll start by creating an empty list in which to store our result. Then we’ll iterate through all four octets of our network IP, performing a binary AND with our original input IP and our netmask from earlier.

net ¼ []
for i in range(4):
net.append(int(addr[i]) & mask[i])

Don’t forget, our original address list was still a string. When we read that in from the command line, it treated the numbers like text. So we used the int function to change that so that we can do the math. The append method adds the calculated value to the end of the list. Now that we know our network IP and our netmask, we can calculate our
broadcast address:

# Determine broadcast parameters from CIDR address and duplicate the
# network address
broad ¼ list(net)
brange ¼ 32 – cidr
for i in range(brange):
broad[3 – i/8] ¼ broad[3 – i/8] þ (1 << (i % 8))

Since the broadcast is going to be based on our network IP, let’s copy the list into a new variable. The list method makes a duplicate list of net so that you don’t change both the broad list and the net list when you make a change. To determine our broadcast address, we add the host bits back into the network address to figure out what the last IP is in this network. To do this, we start with the last bit and count from right to left, setting bits until all bits have been set. Now that we have all our addresses, we need to print the information. The problem is that we have arrays of integers, and we would have to cast each individual element to a string when we print it. We can overcome this limitation with map, which takes every element in an array and runs a function against it:

“.”.join(map(str,mask))

By using map, we convert each integer in our netmask to a string. This leaves us with a list of strings. Next we can use join to join them together. The join function works by using a delimiter to assemble the elements of a list into
a string where each element is separated by that delimiter. We should have all we need to combine our final program. Let’s try it out, and verify that everything is working:

#!/usr/bin/python
import sys
# Get address string and cidr string from command line
(addrString,cidrString) = sys.argv[1].split(‘/’)
# split address into octets and turn cidr into int
addr = addrString.split(‘.’)
cidr = int(cidrString)
#initialize the netmask and calculate based on cidr mask
mask = [0,0,0,0]
for i in range(cidr):
mask[i/8] = mask[i/8] + (1 << (7 – i % 8))
#initialize net and binary and netmask with addr to get network
net = []
for i in range(4):
net.append(int(addr[i]) & mask[i])
#duplicate net into broad array, gather host bits, and generate
#broadcast
broad = list(net)
brange = 32 – cidr
for i in range(brange):
broad[3 – i/8] = broad[3 – i/8] + (1 << (i % 8))
# Print information, mapping integer lists to strings for easy printing
print “Address: ” , addrString
print “Netmask: ” , “.”.join(map(str,mask))
print “Network: ” , “.”.join(map(str,net))
print “Broadcast ” , “.”.join(map(str,broad))

Now, examine the output in Figure .
We now have a working code example that uses lists in a number of ways. While this provides some basics for using lists, we will explore some of these topics further in examples in the next section of this article.

Dictionaries:-

Dictionaries provide associative array functionality for Python. We use dictionaries when we have list elements that we’d like to label. For example, we could be mapping user IDs to employee names, or associating multiple vulnerabilities to a specific host.

 

Untitled
Output of subnet.py

To examine dictionaries, let’s start with a practical example. A company may have a standard initial password for users based on some pattern. This could include using letters and numbers that have some relevance to the individualdfor example, first character of first name plus employee ID assigned by Human Resources. If we
find a shadowed password file, we may be able to determine which users have a default password if we know how the passwords are computed.

Say we have found a password file:

kevin:jP5RTBmoSymUI:42:42:Kevin,,,,42:/home/kevin:/bin/bash
ryan:AlQD3NnPMW5sE:1000:1000:Ryan,,,,431:/home/ryan:/bin/bash
jason:aPg1C.EYrD0bw:1001:1001:Jason,,,,739:/home/jason:/bin/bash
don:JCZ0WIUo0XvBc:1002:1002:Don,,,,831:/home/don:/bin/bash
ed:skOrAUx/yNdD2:1337:1337:Ed,,,,1337:/home/ed:/bin/bash

 

 

NOTE
A quick refresher about the fields in the password file: The file is colon-delimited, and the first
field is the username. The second field is the crypted password. The third field is the GECOS,
which is a comma-delimited field that contains things such as the full name of the user, the
building location or contact person, the office telephone number, and other contact information.
The fourth field is the home directory, and the last field is the shell.

Using a program such as John The Ripper from the BackTrack Live CD, we have discovered that Ryan’s password is R431 and seems to be based on the last number in the GECOS field combined with the first character of the first name. That could be the way the default passwords are created, so let’s write a Python script to check for other users with similar patterns:

#!/usr/bin/python
import sys
import crypt
f = open(sys.argv[1],”r”)
lines = f.readlines()
f.close()

The crypt module will allow us to compute crypted passwords inside Python.We open the filename that was passed on the command line and specify that it should be opened as read-only. We read the file lines into a list called lines and close our file.

for line in lines:
data = line.split(‘:’)
# Set username and password to first 2 fields
user,password = data[0:2]
geco = data[4].split(‘,’)
# Create password guess using first char of first name and last field
guess = geco[0][0] + geco[-1]
#Assign salt as first 2 characters of crypted password
salt = password[0:2]

We need to split apart each line to get the relevant information for building our password guess.We make each field an element in a list called data and then assign the first two fields of data to the user and password variables. We can easily create segments from a list by using the syntax list[start:end] which will split our array apart from the first element up to, but not including, the last element.Next we create our password guess by looking at the first character of the first field of the GECOS information and combining it with the last field of the GECOS information.We can grab the last element in an array by using negative indexes. The negative indexes count backward from the end of the array, but are most commonly used to reference the last element of an array.

if crypt.crypt(guess,salt) == password:
userInfo = { “user” : user, “pass” : guess, “home” : data[5],
“uid” : data[2] , “name” : geco[0]}
found.append(userInfo)

Now that we have our guess, we can test to see if the current user’s password is equal to our guessed value. Crypted passwords use a randomizing value called a salt to make them more difficult to crack. Each time, a different salt value is used so that the encrypted value will be different even if two people have the same password. In the crypted value, the salt is the first two characters, so if we encrypt our guess with the same salt as the crypted password, it we will get the same encrypted value if they match. So, we check our hash, and if they match, we win. Next we need to build up our information so that we can access it easily later. For this script, we are only going to print what we found, but you may be able to adapt this to do something else in the future. We are going to put all our data in a hash so that we can use each piece of our gathered information. To create our dictionary, we are going to specify a list of keys and values within curly brackets ({}). We assign that value to our variable called userInfo. We next add our dictionary to the found list we created earlier through the append function:

for user in found:
print “User : %s ” % user[‘user’]
for k in user.keys():
if k == “user”:
continue
print “t%s : %s” % (k,user[k])

Finally, we should know which users still have their default passwords. Now we have to print them so that we can use them. For this, we use a for loop to iterate through the found list and assign each dictionary to the user variable. We can enumerate the returned list using the keys function to get all the keys that were used in our dictionary. At this point, we can print the rest of our information about a user, excluding the actual user field. Now that we have put our script together, let’s verify the output:

#!/usr/bin/python
import sys
import crypt
# Read file into lines
f = open(sys.argv[1],”r+”)
lines = f.readlines()
f.close()
found = []
for line in lines:
data = line.split(‘:’)
# Set username and password to first 2 fields
user,password = data[0:2]
geco = data[4].split(‘,’)
# Create password guess using first char of first name and last field
guess = geco[0][0] + geco[-1]
# Assign salt as first 2 characters of crypted password
salt = password[0:2]
# Check crypted value to see if matches, if yes put in found
if crypt.crypt(guess,salt) == password:
userInfo = { “user” : user, “pass” : guess, “home” : data[5],
“uid” : data[2] , “name” : geco[0]}
found.append(userInfo)
for user in found:
print “User : %s ” % user[‘user’]
for k in user.keys():
Python basics 49
if k == “user”:
continue
print “t%s : %s” % (k,user[k])
And here’s our output:
$ ./pass.py passwd
User : ryan
uid : 1000
home : /home/ryan
name : Ryan
pass : R431
User : jason
uid : 1001
home : /home/jason
name : Jason
pass : J739
User : don
uid : 1002
home : /home/don
name : Don
pass : D831

We can see that there were users with default passwords in our sample. We have gone over the basics of dictionaries, but dictionaries can be created in other ways. Figure  shows two more examples of creating dictionaries that may
be helpful. On the first instance, we are specifying key-value pairs separated by commas. This is straightforward and similar to how we did it in our script. The second method uses a list of tuples, ordered pairs of information, passed to the dict function. Each key-value pair is enclosed in parentheses, letting the function know they should be grouped together. There is no best way to create a dictionary; some approaches

Using Interactive Python to Try Different Invocation Methods of dicts
Using Interactive Python to Try Different Invocation Methods of dicts

 

Table 2.1 Python Conditional Operators

Operator Meaning Operator Meaning
 <  Less than  >  Greater than
 ==  Equivalent   !=  Not equivalent
<=  Less than or equivalent  >= Greater than or equivalent

may be easier in some cases, but the approach you use will mostly be a matter of aesthetics. Control statements We have encountered a number of control statement types in the scripts we have created thus far. There are other ways to control the execution of a script. In this section, we will look at conditionals and loops. Conditionals are decision points that determine what part of our code to follow. The two most common ways to generate these statements are through comparison operators and functions that return either true or false. The comparison operators are going to be consistent with most other languages, but you can reference them in Table.

If Statements

We have seen these conditionals in action throughout this chapter, but they have been used in simple if statements. Let’s look at a more complex example.

#!/usr/bin/python
import os
myuid = os.getuid()
if myuid == 0:
print “You are root”
elif myuid < 500:
print “You are a system account”
else:
print “You are just a regular user”

This code begins by getting the logged-in user’s user ID from the operating system. It then checks to see if it is equivalent to 0. If it is, that comparison returns true; it will print “You are root”. The elif statement allows us to add extra conditionals within the same indentation for more checks. If none of our if and elif statements return true, the default condition is else. Now let’s look at a modified example where we use a function that checks to see if we can read the shadow file. We test this with the os.access method. We want to know if we can read the file, so we use the constant os.R_OK to indicate that we want to know if the file is readable. If we can read the shadow file, we can eventually get the root password. This is what some penetration testers call “winning.” Otherwise,we will have to try something else.

#!/usr/bin/python
import os
if os.getenv(‘USER”) == “root”:
print “You are root”
elif os.getuid() == 0:
print “You are sudo as root”
elif os.access(‘/etc/shadow’,os.R_OK):
print “You aren’t root, but you can read shadow”
else:
print “No soup for you”

 

Loops:-

Loops are more useful for repeated actions. Two basic loop types are for loops and while loops. For loops iterate through a list and while loops run until a condition is met or until we break out of the loop. We used a for loop in earlier scripts (e.g., pass.py), but we haven’t seen a while loop yet:

while 1:
if i > 0 and i < 10:
i = i + 5
continue
elif i % 2 == 0 :
print “EVEN”
elif i % 3 == 0:
print “ODD”
elif i % 25 == 0:
break
print str(i)
i = i + 1

This while loop will run forever, because 1 is always true. Therefore, we will have to make sure there are conditions to break out of this loop; it will never stop on its own. Our first if statement checks to determine if the variable i is between 1 and 9; if it is, we will add five to it and continue. The continue operator says: “Stop here, and go to the next iteration of our loop.” Experiment with this script to see how adding other conditions can change the flow of the program

Functions:-

So far the scripts we have written are small. As we move on to larger programs with sections of code we want to reuse, functions become critical. Functions give us logical and reusable groupings of code. Functions begin with the def statement, followed by the name of the function and the list of arguments the function requires. Let’s look at a practical example.Sites we are pen-testing will frequently advertise where all the goodies are without us needing to ask. The robots.txt file is where people can tell search engines where not to index. These are frequently the exact places we want to look when we are trying to find the interesting stuff. Here is a function that will get the robots file
and give us back the paths we aren’t meant to find:

def getDenies(site):
paths = []
# Create a new robot parser instance and read the site’s robots file
robot = robotparser.RobotFileParser()
robot.set_url(“http://”+site+”/robots.txt”)
robot.read()
# For each entry, look at the rule lines and add the path to paths if
# disallowed
for entry in robot.entries:
for line in entry.rulelines:
not line.allowance and paths.append(line.path)
return set(paths)

Our function, getDenies, takes one argument: the site hosting the robots.txt file. This argument is required because it has no default value. We could make this value optional by adding an assignment operator and a default value. This would look like site = ‘localhost’ instead of the current site variable. Once we have our site, we create a new RobotFileParser instance and set the URL to be the fully qualified path to the robots.txt file by using the set_url method. We use the read method to read the information into our parser which takes care of parsing all the data for us. Python uses indentation to determine context, so we know that our function has ended when our indentation returns to the same indentation as our function statement.

Now that we have the parsed data in our parser object, we are going to directly access the entry groupings that it gathered. Each entry grouping is made up of rule lines. We are going to use nested for loops to get to each individual rule and thencheck to see if it is an “allow” or a “deny.” We do this by checking the allowance variable, and if it is false we add the path to our paths list. Once we’ve gone through all the rule lines, we use the set function to consolidate all the duplicates in our list into a single list of unique elements. Finally, our return function gives that
information back to the calling code. But not all functions have to have a return value. Now that our function is complete, we can generate the rest of the code that is necessary to make our program useful and try it out:

#!/usr/lib/python
import robotparser
sites = [‘www.google.com’,’www.offensive-security.com’,’www.yahoo.com’]
def getDenies(site):
paths = []
# Create a new robot parser instance and read the site’s robots file
robot = robotparser.RobotFileParser()
robot.set_url(“http://”+site+”/robots.txt”)
robot.read()
# For each entry, look at the rule lines and add the path to paths if
# disallowed
for entry in robot.entries:
for line in entry.rulelines:
not line.allowance and paths.append(line.path)
return set(paths)
for site in sites:
print “Denies for ” + site
print “t” + “nt”.join(getDenies(site))

Read part 1 here :- Learn Python (Part 1)
R
ead part 2 here :- Learn Python (Part 2)

NExt will be soon

Back to top button
Close