Chapter 4. Executing Credential Attacks with Python
There are multiple forms of credential attack, but all too often, they are considered as the last step in a penetration test, when all else has failed. This is because most new assessors approach it in the wrong manner. When discussing what brand new assessors use for credential attacks, the two most common attacks used are online dictionary and brute force attacks. They execute a credential attack by downloading a giant word list containing passwords and an extensive username list and run it against an interface. When the attack fails, the assessor follows up and executes a brute force attack.
This attack uses either the same username list or the super user (root) or the local administrator account. The majority of the time this will fail as well, so in the end dictionary attacks get a bad rap and get moved to the end of the engagement. This is ever so wrong, as on most engagements, especially on Internet facing postures a credential attack is going to get you access if done right. Chapter 1, Understanding the Penetration Testing Methodology and Chapter 3, Identifying Targets with Nmap, Scapy, and Python introduced you to do some basic dictionary attack concepts, this chapter will build on them, and help you understand how and when to use them. Before we get started with how you execute these attacks, you need to have a firm understanding of the attack types.
The types of credential attacks
When discussing credential attacks, there is an instant gravitation to password attacks. Remember authentication and authorization to a resource usually requires two components, the password and the username. Having the most well used password in the entire world does you no good, if you do not know the username it belongs to. As such, credential attacks are the manner we assess resources using both usernames and passwords. Targeted sourcing of usernames will be covered later, but for now we have to define the overarching types of password attacks, online and offline.
Defining the online credential attack
The online credential attack is what is done when you are targeting interfaces or resources to forcefully authenticate. What this means is you may not know the username, password, or both and are trying to determine the correct information that will grant you access. These attacks are executed when you have not gained access to a resource that would provide you hashes, clear text passwords, or other protected forms of data. Instead, you are trying to make educated guesses against a resource based on research you have done. Types of online attacks include dictionary, brute force and password spray attacks. Remember that resources can be part of a federated or centralized system like Active Directory (AD) or a local account on the host itself.
Tip
For you screaming what about hybrid? Most assessors consider it a form of dictionary attack as it is just a list of words permutated anyway. You rarely find a dictionary that does not contain hybrid words today anyway. In the 1990s, this was rarer, but with better education and more powerful systems with substantiated password requirements have changed this situation.
Defining the offline credential attack
An offline credential attack is when you have already cracked a resource and extracted the data such as the hashes and are now attempting to guess them. This can be done in a number of manners, depending on the type of hash and the resources available, some examples include offline dictionary, rule based attacks, brute force, or rainbow table attacks. One of the reasons we call this offline credential attacks instead of offline password attacks, is because you are trying to guess the clear text version of the password on a system it did not originate from.
Those password hashes may have been salted with random information or by known components such as the usernames to create the salt. Ergo, you may still need to know the username to crack the hash because the salt is a component of added randomness. Now, I have seen a few implementations that use the username as the salt for a hashing algorithm and this is a really bad idea. The argument you will hear that says this is a good idea comes from the fact that the salt is stored with the password anyway just like the username, so why does it matter? Known usernames that are used ubiquitously through systems such as root, administrator, and admin are known prior to compromising of the system, along with the known encryption method which opens up a major vulnerability.
This means the salt is based off a username, means it is known prior to getting access to the environment and before the engagement began. So that means, you have effectively defeated the mechanism put in place to making cracking passwords more difficult to include the use of rainbow tables. Making salts known prior to an engagement means that rainbow tables are again useful for salted passwords as well, if you have a tool that can process the data.
Tip
Poor salting methods and custom encryption methods can open an organization up to compromise.
Offline attacks hinge on the premise of taking a word and creating a hash in the same format as the protected password using the same method of protection. If the protected value is the same as the newly created value, then you have a word that will be equivalent and grant access. Most password protection methods use hashing to obscure the value, which is a one way function, or in other words, it cannot be, so the method cannot be reversed to produce the original value.
So when a system accepts a password through its authentication method, it hashes the password in the same method and compares the stored hash value to the newly computed one. If they equal each other, you have a reasonable level of assurance that the passwords are the same and access will be granted. The idea of a reasonable level assurance is dependent on how strong the hashing algorithm is. Some hashing algorithms are considered weak or broken, such as Message Digest 5 (MD5) and Secure Hashing Algorithm 1 (SHA-1). The reason for this is that they are susceptible to collisions.
A collision means that the mathematical possibility for the data it protects does not have enough entropy to guarantee that a different hashed value will not equal the same thing. The reality is that two completely different words hashed by the same broken algorithm could create the same hash value. As such, this directly affects systems authentication methods.
When someone accesses the system, the password input is hashed in the same method as the password that is stored on the system. If the two values match, that means the theoretically the password is the same, unless the hashing algorithm is weak. So, when assessing the system, you just have to find a value that creates the same hash as the original value. If that occurs, you will be granted access to the system, and this is where the weakness of hashes that have known collisions come in. You do not need to know the actual value that created the hash, just an equivalent value that will create the same hash.
Tip
At the time of writing, MD5 is used to verify integrity of file systems and data for forensics. Even though MD5 is considered a broken hash, it is still considered good enough for forensics and file system integrity. The reason for this is that it would take an infeasible amount of work to fool the algorithm with substantial data sets like files systems. To manipulate a file system after data had been adjusted or extracted to create the same integrity marker is unrealistic.
Now that you have an understanding of both offline and online credential attack differences, we need to start generating our data to be used for them. This starts with generating usernames, and then verifying them as part of the organization. This seems like a minor step, but it is very important as it trims your list of targets down, reduces the noise you generate, and improves your chances of compromising the organization.
Identifying the target
We are going to use Metasploitable as an example here, because it will allow you to test these concepts in a safe and legal environment. To start with, let us do a simple nmap
scan of the system with a service detection. The following command highlights the specific arguments and options, which does SYN scan looking for the well-known ports on a system.
nmap -sS -vvv -Pn -sV<targetIP>
As you can see from the results, the host is identified as Metasploitable and a number of ports are open to include Simple Mail Transfer Protocol (SMTP) at port 25.

Creating targeted usernames
When targeting organizations, especially at the perimeter, the easiest way in is to compromise an account. This means that you get at least the basic level of access of that person and can find ways to elevate your privileges. To do that, you need to identify realistic usernames for an organization. The multiple ways to do this include researching of people who work for the organization through sites like http://www.data.com/, https://www.facebook.com/, https://www.linkedin.com/hp/, and http://vault.com/. You can automate some of this with tools like the Harvester.py
and Recon-ng
, which source Internet exposures and repositories.
This initial research is good, but the amount of time you typically have to do this is limited, unlike malicious actors. So what you can do to supplement the data you find is generate usernames and then verify them against a service port like SMTP with VRFY enabled or Finger. If you find these ports open, especially on the Internet for the target organization, the first thing I do is verify my username list. This means I can cut down my attack list for the next step, which we will cover in Chapter 5, Exploiting Services with Python.
Generating and verifying usernames with help from the U.S. census
For years, the U.S. Government and other countries survey the countries populace for details. This information is available to law abiding citizens, as well as malicious actors. These details can be used for anything from social engineering attacks, sales research, and even telemarketers. Some details are harder to find than others, but our favorite bit is the surname list. This list produced in 2000, provides us the top 1000 surnames in the U.S. populace.
If you have ever looked at the components of most organization's usernames, it is the first letter of their first name and the entire last name. When these two components are combined, it creates a username. Using the U.S. Census top 1000 list, we can cheat the creation method by downloading the list extracting the surnames and prepending every letter in the alphabet to create 26 usernames for each surname. This process will produce a list of 26,000 usernames not including the details of publically sourced information.
When you combine the username list created by searching social media, and using tools to identify e-mail addresses, you could have a substantial list. So you would need to trim it down. In this example, we are going to show you how to extract details from an Excel spreadsheet using Python, and then verify the usernames created and combined by other lists against the SMTP service with VRFY running.
Tip
Westernized Governments often produce similar lists, so make sure you look where you are trying to assess and use the information relevant to the organization's location. In addition to that, states such as U.S. territories, Alaska and Hawaii have vastly different surnames than the rest of the continental U.S. Build your list to compensate for these differences.
Generating the usernames
The first step to this process is downloading the excel spreadsheet, which can be found here download the specific file directly from the console using wget
as shown following. Keep in mind that you should only download the file; never assess an organization or website unless you have permission. The following command does the equivalent of visiting the site and clicking the link to download the file:
wget http://www2.census.gov/topics/genealogy/2000surnames/Top1000.xls
Now open up the Excel file and see how it is formatted, so that we know how to develop the script to pull the details out.

As you can see, there are 11 columns that define the features of the spreadsheet. The two we care about are the name and the rank. The name is the surname we will create our username list from, and the rank is the order of occurrence in the U.S. Before we build a function to parse the census file, we need to develop a means to get the data into the script.
The argparser
library allows you to develop command line options and arguments quickly and effectively. The xlrd
library will be used to analyze the Excel spreadsheet, and the string library will be used to develop a list of alphabetical characters. The os
library will confirm what Operating System (OS) the script is being run from, so filename formatting can be handled internally. Finally, the collections library will provide the means to organize the data in memory pulled out of the Excel spreadsheet. The only library that is not native to your Python instance is the xlrd
one, which can be installed with pip
.
#!/usr/bin/env python import sys, string, argparse, os from collections import namedtuple try: import xlrd except: sys.exit("[!] Please install the xlrd library: pip install xlrd")
Now that you have your libraries situated, you can now build out the functions to do the work. This script will include the ability to have its level of verbosity increased or decreased as well. This is a relatively easy feature to include, and it is done by setting the verbose variable to an integer value; the higher the value, the more verbose. We will default to a value of 1 and support up to a value of 3. Anything more than that will be treated as a 3. This function will accept the name of the file being passed as well, as you never know it may change in the future.
We are going to use a form of a tuple called a named tuple to accept each row of the spreadsheet. A named tuple allows you to reference the details by coordinates or field name depending on how it is defined. As you can guess, this is perfect for a spreadsheet or database data. To make this easy for us, we are going to define this the same way as the spreadsheet.
defcensus_parser(filename, verbose): # Create the named tuple CensusTuple = namedtuple('Census', 'name, rank, count, prop100k, cum_prop100k, pctwhite, pctblack, pctapi, pctaian, pct2prace, pcthispanic')
Now, develop the variables to hold the workbook, spreadsheet by the name, and the total rows and the initial row of the spreadsheet.
worksheet_name = "top1000" #Define work book and work sheet variables workbook = xlrd.open_workbook(filename) spreadsheet = workbook.sheet_by_name(worksheet_name) total_rows = spreadsheet.nrows - 1 current_row = -1
Then, develop the initial variables to hold the resulting values and the actual alphabet.
# Define holder for details username_dict = {} surname_dict = {} alphabet = list(string.ascii_lowercase)
Next, each row of the spreadsheet will be iterated through. The surname_dict
holds the raw data from the spreadsheet cells. The username_dict
will hold the username and the rank converted to strings. Each time a point is not detected in the rank value, it means that the value is not a float
and is therefore empty. This means the row itself does not contain real data, and it should be skipped.
while current_row<total_rows: row = spreadsheet.row(current_row) current_row += 1 entry = CensusTuple(*tuple(row)) #Passing the values of the row as a tuple into the namedtuple surname_dict[entry.rank] = entry cellname = entry.name cellrank = entry.rank for letter in alphabet: if "." not in str(cellrank.value): if verbose > 1: print("[-] Eliminating table headers") break username = letter + str(cellname.value.lower()) rank = str(cellrank.value) username_dict[username] = rank
Remember, dictionaries store values referenced by key, but unordered. So what we can do is take the values stored in the dictionary and order them by the key, which was the rank of the value or the surname. To do this, we are going to take a list and have it accept the sorted details returned by a function. Since this is a relatively simple function, we can create a nameless function with lambda
, which uses the optional sorted parameter key to call it as it processes the code. Effectively, sorted creates an ordered list based on the dictionary key for each value in the dictionary. Finally, this function returns the username_list
and both dictionaries if they would be needed in the future.
username_list = sorted(username_dict, key=lambda key: username_dict[key]) return(surname_dict, username_dict, username_list)
The good news is that is the most complex function in the entire script. The next function is a well-known design that takes in a list removes duplicates. The function uses the list comprehension, which reduces the size of simple loops used to create ordered lists. This expression within the function could have been written as the following:
for item in liste_sort: if not noted.count(item): noted.append(item)
To reduce the size of this simple execution and to improve readability, we instead change it to a list comprehension, as shown in the following excerpt:
defunique_list(list_sort, verbose): noted = [] if verbose > 0: print("[*] Removing duplicates while maintaining order") [noted.append(item) for item in list_sort if not noted.count(item)] # List comprehension return noted
One of the goals from this script is to combine research from other sources into the same file that contains usernames. The user can pass a file that can be prepended or appended to the details of the census file outputs. When this script is run, the user can supply the file as a prepended value or an appended value. The script determines which one it is, and then reads in each line stripping new line character from each entry. The script then determines if it needs to be added to the end or front of the census username list and sets the variable value for put_where
. Finally, both the list and values for put_where
are returned.
defusername_file_parser(prepend_file, append_file, verbose): if prepend_file: put_where = "begin" filename = prepend_file elif append_file: put_where = "end" filename = append_file else: sys.exit("[!] There was an error in processing the supplemental username list!") with open(filename) as file: lines = [line.rstrip('\n') for line in file] if verbose > 1: if "end" in put_where: print("[*] Appending %d entries to the username list") % (len(lines)) else: print("[*] Prepending %d entries to the username list") % (len(lines)) return(lines, put_where)
All that is needed is a function that combines the two user lists together. This function will either prepend the data with a simple split that sticks the new user list in front of the census list or appends the data with the extend function. The function will then call previous function that was created, which reduces non-unique values to unique values. It would be bad to know a password lockout limit for a function, and then call the same user accounts more than once, locking out the account. The final item returned is the new combined username list.
defcombine_usernames(supplemental_list, put_where, username_list, verbose): if "begin" in put_where: username_list[:0] = supplemental_list #Prepend with a slice if "end" in put_where: username_list.extend(supplemental_list) username_list = unique_list(username_list, verbose) return(username_list)
The last function in this script writes the details to a file. To further improve the capabilities of this script, we can create two different types of username files: one that includes the domain like an e-mail address and the other a standard username list. The supplemental username list with the domain will be treated as optional.
This function deletes the contents of the files as necessary and iterates through the list. If the list is to be a domain list, it simply applies the @
and the domain name to each username as it writes it to the file.
defwrite_username_file(username_list, filename, domain, verbose): open(filename, 'w').close() #Delete contents of file name if domain: domain_filename = filename + "_" + domain email_list = [] open(domain_filename, 'w').close() if verbose > 1: print("[*] Writing to %s") % (filename) with open(filename, 'w') as file: file.write('\n'.join(username_list)) if domain: if verbose > 1: print("[*] Writing domain supported list to %s") % (domain_filename) for line in username_list: email_address = line + "@" + domain email_list.append(email_address) with open(domain_filename, 'w') as file: file.write('\n'.join(email_list)) return
Now that the functions have been defined, we can develop the main part of the script and properly introduce arguments and options.
Note
The argparse
library has replaced the optparse
library, which provided similar capabilities. It should be noted that a lot of the weaknesses related to options and arguments in scripting languages are addressed very well with this library.
The argparse
library provides you the ability to setup both short and long options that can accept a number of values defined by types
. These are then presented into a variable you have defined with dest
.
Each of these arguments can have specific capabilities defined with the action parameter to include storage of values counting and others. Additionally, each of these arguments can have default
values set with the default
parameter as necessary. The other feature that is useful is the help
parameter, which provides feedback in usage and improves documentation. We do not use every script that we create on every engagement or every day. See the following example on how to add an argument for the census
file.
parser.add_argument("-c", "--census", type=str, help="The census file that will be used to create usernames, this can be retrieved like so:\n wget http://www2.census.gov/topics/genealogy/2000surnames/Top1000.xls", action="store", dest="census_file")
With these simple capabilities understood, we can develop the requirements for arguments to be passed to the script. First, we verify that this is part of the main function, and then we instantiate the argeparse
as parser. The simple usage statement shows what would need to be called to execute the script. The %(prog)s
is functionally equivalent to positing 0
in argv
, as it represents the script name.
if __name__ == '__main__': # If script is executed at the CLI usage = '''usage: %(prog)s [-c census.xlsx] [-f output_filename] [-a append_filename] [-p prepend_filename] [-ddomain_name] -q -v -vv -vvv''' parser = argparse.ArgumentParser(usage=usage)
Now that we have defined the instance in parser, we need to add each argument into the parser. Then, we define the variable args
, which will hold the publically referenced values of each stored argument or option.
parser.add_argument("-c", "--census", type=str, help="The census file that will be used to create usernames, this can be retrieved like so:\n wget http://www2.census.gov/topics/genealogy/2000surnames/Top1000.xls", action="store", dest="census_file") parser.add_argument("-f", "--filename", type=str, help="Filename for output the usernames", action="store", dest="filename") parser.add_argument("-a","--append", type=str, action="store", help="A username list to append to the list generated from the census", dest="append_file") parser.add_argument("-p","--prepend", type=str, action="store", help="A username list to prepend to the list generated from the census", dest="prepend_file") parser.add_argument("-d","--domain", type=str, action="store", help="The domain to append to usernames", dest="domain_name") parser.add_argument("-v", action="count", dest="verbose", default=1, help="Verbosity level, defaults to one, this outputs each command and result") parser.add_argument("-q", action="store_const", dest="verbose", const=0, help="Sets the results to be quiet") parser.add_argument('--version', action='version', version='%(prog)s 0.42b') args = parser.parse_args()
With your arguments defined, you are going to want to validate that they were set by the user and that they are easy to reference through your script.
# Set Constructors census_file = args.census_file # Census filename = args.filename # Filename for outputs verbose = args.verbose # Verbosity level append_file = args.append_file # Filename for the appending usernames to the output file prepend_file = args.prepend_file # Filename to prepend to the usernames to the output file domain_name = args.domain_name # The name of the domain to be appended to the username list dir = os.getcwd() # Get current working directory # Argument Validator if len(sys.argv)==1: parser.print_help() sys.exit(1) if append_file and prepend_file: sys.exit("[!] Please select either prepend or append for a file not both")
Similar to an argument validator, you are going to want to make sure that an output file is set. If it is not set, you can have a default value ready to be used as needed. You are going to want to be OS agnostic, so it needs to be setup to run in either a Linux/UNIX system or a Windows system. The easiest way to determine that is by the direction of the \
or /
. Remember that the \
is used to escape characters in scripts, so make sure to put two to cancel out the effect.
if not filename: if os.name != "nt": filename = dir + "/census_username_list" else: filename = dir + "\\census_username_list" else: if filename: if "\\" or "/" in filename: if verbose > 1: print("[*] Using filename: %s") % (filename) else: if os.name != "nt": filename = dir + "/" + filename else: filename = dir + "\\" + filename if verbose > 1: print("[*] Using filename: %s") % (filename)
The remaining components that need to be defined are your working variables as the functions are called.
# Define working variables sur_dict = {} user_dict = {} user_list = [] sup_username = [] target = [] combined_users = []
Following all those details, you can finally get to the meat of the script, which is the calling of the activity to create the username file:
# Process census file if not census_file: sys.exit("[!] You did not provide a census file!") else: sur_dict, user_dict, user_list = census_parser(census_file, verbose) # Process supplemental username file if append_file or prepend_file: sup_username, target = username_file_parser(prepend_file, append_file, verbose) combined_users = combine_usernames(sup_username, target, user_list, verbose) else: combined_users = user_list write_username_file(combined_users, filename, domain_name, verbose)
The following screenshot demonstrates how the script could output a help file:

An example of how to run the script and the output can be found here, with the prepending of a username.lst
with the username msfadmin
in it.

Tip
This script can be downloaded from https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/username_generator.py.
We have our username generator, and we include the name msfadmin
because we have done some initial research on the test box Metasploitable. We know that is a standard default account, and we are going to want to verify if it is actually in the system. When you initially scan a system and you identify open ports and services, and then verify what you are getting ready to attack, this is a normal part of research. That research should include looking for default and known accounts as well.
Tip
When executing these types of attacks, it is normal to exclude built in accounts for systems that are known like root. On the Windows systems, you should still test the Administrator account because that one may be renamed. You should also avoid testing for root logins during Double Blind or Red Team exercise at first. This will often elicit an alert for security administrative staff.
Testing for users using SMTP VRFY
Now that we have a list of usernames and we know that SMTP is open, we need to see if VRFY
is enabled. This is extremely simple, all you do is telnet into port 25 and execute the command VRFY
followed by a word and hit enter. The great part about checking for usernames this way is that if VRFY
is enabled, something is wrong with the secure deployment practices, and if it is Internet facing, they are likely not monitoring it. Reduce the number of credential attack guesses in an online credential attack against an interface will reduce the chances of being caught. The simple commands to execute this are shown in the following figure:

We did not get a hit for smith, but perhaps others will confirm during this attack. Before we write our script, you need to know the different error or control messages that can be produced in most SMTP systems. These can vary and you should design your script well enough to be modified for that environment.

Now that you know the basic code responses, you can write a script that takes advantage of this weakness.
Note
You may be wondering why we are writing a script to take advantage of this when Metasploit and other tools have built in modules for this. On many systems, this weakness has special timeouts and or throttling requirements to take advantage of. Most other tools to include the Metasploit module fail when you are trying to get around these roadblocks, so then Python is really your best answer.
Creating the SMTP VRFY script
Since Metasploit and other attack tools do not take into consideration timeouts for the session attempt and delays between each attempt, we need to consider making the script more useful by incorporating those tasks. As mentioned previously, tools are great and they will often fit 80 percent of the situations you will come across, but being a professional means adapting situations a tool may not fit.
The libraries being used have been common so far, but we added one from Chapter 2, The Basics of Python Scripting—the socket library for network interface control and time for control of timeouts.
#/usr/bin/env python import socket, time, argparse, os, sys
The next function reads the files into a list that will be used for testing usernames.
defread_file(filename): with open(filename) as file: lines = file.read().splitlines() return lines
Next, a modification of the username_generator.py
script function, which wrote the data to a combined username file. This provides a confirmed list of usernames to a useful output format.
defwrite_username_file(username_list, filename, verbose): open(filename, 'w').close() #Delete contents of file name if verbose > 1: print("[*] Writing to %s") % (filename) with open(filename, 'w') as file: file.write('\n'.join(username_list)) return
The last function and most complex one is called verify_smtp
, which validates usernames against the SMTP VRFY
vulnerability. First, it loads up the usernames returned from the read_file
function and confirms the parameter data.
defverify_smtp(verbose, filename, ip, timeout_value, sleep_value, port=25): if port is None: port=int(25) elif port is "": port=int(25) else: port=int(port) if verbose > 0: print "[*] Connecting to %s on port %s to execute the test" % (ip, port) valid_users=[] username_list = read_file(filename)
The script then takes each username out of the list and uses a conditional test to try and create connection to the system at the specified IP and port. We capture the banner when it connects, build the command with the username, and send the command. The returned data is stored in the results variable, which is tested for the previous documented response codes. If a 252 response is received, the username is appended to the valid_users
list.
for user in username_list: try: sys.stdout.flush() s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(timeout_value) connect=s.connect((ip,port)) banner=s.recv(1024) if verbose > 0: print("[*] The system banner is: '%s'") % (str(banner)) command='VRFY ' + user + '\n' if verbose > 0: print("[*] Executing: %s") % (command) print("[*] Testing entry %s of %s") % (str(username_list.index(user)),str( len(username_list))) s.send(command) result=s.recv(1024) if "252" in result: valid_users.append(user) if verbose > 1: print("[+] Username %s is valid") % (user) if "550" in result: if verbose > 1: print "[-] 550 Username does not exist" if "503" in result: print("[!] The server requires authentication") break if "500" in result: print("[!] The VRFY command is not supported") break
Specific break conditions are set to cause a relative graceful end of this script if conditions are met that necessitate the ending of the test. It should be noted that each username has a separate connection being established so as to prevent a connection from being held open too long, reduce errors, and improve the chances that in the future, this script can be made into a multithreaded script, as described in Chapter 10, Adding Permanency to Python Tools.
The last two components of this script are the exception error handling, and the final conditional operation, which closes the connection, delays the next execution if necessary and clears the STDOUT.
except IOError as e: if verbose > 1: print("[!] The following error occured: '%s'") % (str(e)) if 'Operation now in progress' in e: print("[!] The connection to SMTP failed") break finally: if valid_users and verbose > 0: print("[+] %d User(s) are Valid" % (len(valid_users))) elif verbose > 0 and not valid_users: print("[!] No valid users were found") s.close() if sleep_value is not 0: time.sleep(sleep_value) sys.stdout.flush() return valid_users
Much of the previous script components are reused here, and they are just tweaked for the new script. Take a look and determine the different components for yourself. Then understand how to incorporate changes into future changes.
if __name__ == '__main__': # If script is executed at the CLI usage = '''usage: %(prog)s [-u username_file] [-f output_filename] [-iip address] [-p port_number] [-t timeout] [-s sleep] -q -v -vv -vvv''' parser = argparse.ArgumentParser(usage=usage) parser.add_argument("-u", "--usernames", type=str, help="The usernames that are to be read", action="store", dest="username_file") parser.add_argument("-f", "--filename", type=str, help="Filename for output the confirmed usernames", action="store", dest="filename") parser.add_argument("-i", "--ip", type=str, help="The IP address of the target system", action="store", dest="ip") parser.add_argument("-p","--port", type=int, default=25, action="store", help="The port of the target system's SMTP service", dest="port") parser.add_argument("-t","--timeout", type=float, default=1, action="store", help="The timeout value for service responses in seconds", dest="timeout_value") parser.add_argument("-s","--sleep", type=float, default=0.0, action="store", help="The wait time between each request in seconds", dest="sleep_value") parser.add_argument("-v", action="count", dest="verbose", default=1, help="Verbosity level, defaults to one, this outputs each command and result") parser.add_argument("-q", action="store_const", dest="verbose", const=0, help="Sets the results to be quiet") parser.add_argument('--version', action='version', version='%(prog)s 0.42b') args = parser.parse_args() # Set Constructors username_file = args.username_file # Usernames to test filename = args.filename # Filename for outputs verbose = args.verbose # Verbosity level ip = args.ip # IP Address to test port = args.port # Port for the service to test timeout_value = args.timeout_value # Timeout value for service connections sleep_value = args.sleep_value # Sleep value between requests dir = os.getcwd() # Get current working directory username_list =[] # Argument Validator if len(sys.argv)==1: parser.print_help() sys.exit(1) if not filename: if os.name != "nt": filename = dir + "/confirmed_username_list" else: filename = dir + "\\confirmed_username_list" else: if filename: if "\\" or "/" in filename: if verbose > 1: print(" [*] Using filename: %s") % (filename) else: if os.name != "nt": filename = dir + "/" + filename else: filename = dir + "\\" + filename if verbose > 1: print("[*] Using filename: %s") % (filename)
The final component of the script is the calling of the specific functions to execute the script.
username_list = verify_smtp(verbose, username_file, ip, timeout_value, sleep_value, port) if len(username_list) > 0: write_username_file(username_list, filename, verbose)
The script has a default help capability, just like the username_generator.py
script, as shown in the following screenshot:

The final version of this script will produce an output like this:

After executing the following command, which has a username flat file passed to it, the IP address of the target, the port of the SMTP service, and the output file, the script has a default sleep value of 0.0
and a default timeout value of 1
second. If testing over the Internet, you may have to increase this value.

The one user we validated on the system as of no surprise was the msfadmin
account. Had this been a real system though, you have reduced the number of accounts you would need to test effectively narrowing down one half the credential attack equation. Now, all you need to do is find a service you want to test against.
Tip
This script can be downloaded from https://raw.githubusercontent.com/funkandwagnalls/pythonpentest/master/smtp_vrfy.py.
Summary
This chapter covered a lot of details on manipulating files from external sources to connecting to resources at a low level. The end result was the ability to identify potential user accounts and validate them. These activities also highlighted the proper use of arguments and options with the argparse
library, and where the use of scripts can meet needs that developed tools cannot. All of this has been built to exploit the services, that we will cover in the next chapter.
- 零基礎搭建量化投資系統:以Python為工具
- Android 9 Development Cookbook(Third Edition)
- Building Mapping Applications with QGIS
- Drupal 8 Module Development
- 硅谷Python工程師面試指南:數據結構、算法與系統設計
- Unity 3D腳本編程:使用C#語言開發跨平臺游戲
- Creating Data Stories with Tableau Public
- 零基礎學Python編程(少兒趣味版)
- Building Dynamics CRM 2015 Dashboards with Power BI
- Python網絡爬蟲技術與應用
- 并行編程方法與優化實踐
- Flask Web開發:基于Python的Web應用開發實戰(第2版)
- 從零開始學UI:概念解析、實戰提高、突破規則
- Flask開發Web搜索引擎入門與實戰
- Python滲透測試編程技術:方法與實踐(第2版)