#34 Gray Hat C#. Руководство для хакера по созданию и автоматизации инструментов безопасности. Автоматизация Nexpose. NexposeSession Class.
Здравствуйте, дорогие друзья.
Мы начнем с написания класса Nexpose Session для взаимодействия с Nexpose API, как показано в листинге ниже.
Конструктор класса NexposeSession [1] принимает до пяти аргументов.
Примечания: три обязательны (имя пользователя, пароль и хост для подключения), а два являются необязательными (порт и версия API со значениями по умолчанию).
3780 [2] и NexposeAPIVersion.v11 [3] соответственно). Начиная с [4],
мы присваиваем свойства Host, Port и APIVersion трем обязательным аргументам. Затем мы отключаем проверку сертификата SSL [5], установив ServerCertificateValidationCallback так, чтобы он всегда возвращал true. Это нарушает хорошие принципы безопасности, но мы отключаем проверку, поскольку Nexpose по умолчанию работает по HTTPS с самозаверяющим сертификатом. (В противном случае проверка сертификата SSL завершится неудачно во время HTTP-запроса.)
Мы [6] пытаемся пройти аутентификацию, вызывая метод Authenticate(), показанный в листинге ниже.
Метод Authenticate() [1] принимает в качестве аргументов имя пользователя и пароль. Чтобы отправить имя пользователя и пароль в API для аутентификации, мы создаем XDocument [2] с корневым узлом LoginRequest и атрибутами идентификатора пользователя и пароля. Мы передаем XDocument методу ExecuteCommand() [3], а затем сохраняем результат, возвращенный сервером Nexpose.Мы [4] определяем, имеет ли XML-ответ Nexpose значение атрибута успеха, равное 1. Если это так [5], мы сначала присваиваем свойству SessionID идентификатору сеанса в ответе и устанавливаем для IsAuthenticated значение true. Наконец, мы возвращаем ответ XML [6].
Метод The ExecuteCommand()
Метод ExecuteCommand(), показанный в листинге ниже, является основой класса NexposeSession.
Прежде чем мы сможем отправить данные в Nexpose, нам нужно знать, какую версию API использовать [1], поэтому мы всегда используем блок switch/case (аналогично серии операторов if) для проверки значения APIVersion. Например, значение NexposeAPIVersion.v11 или NexposeAPIVersion.v12 сообщит нам, что нам нужно использовать URI API для версии 1.1 или 1.2.
Выполнение HTTP-запроса к Nexpose API
Определив URI, к которому нужно сделать запрос API, мы можем теперь отправить данные запроса XML в Nexpose, как показано в листинге ниже.
Общение с HTTP API для Nexpose происходит в два этапа. Сначала Nexpose отправляет запрос API с помощью XML, который сообщает Nexpose, какую команду мы выполняем; затем он считывает ответ с результатами запроса API. Чтобы выполнить фактический HTTP-запрос к Nexpose API, мы создаем HttpWebRequest [1] и присваиваем его свойству Method значение POST [2], свойству ContentType — text/xml [3], а свойству ContentLength — длине нашего XML. Затем мы записываем байты команды API XML в поток HTTP-запросов и отправляем этот поток в Nexpose с помощью Write() [4]. Nexpose проанализирует XML, определит, что делать, а затем вернет результаты в ответ.
Чтение HTTP-ответа из Nexpose API
Далее нам нужно прочитать ответ HTTP на только что сделанный запрос API. В листинге ниже показано, как мы завершаем метод ExecuteCommand(), читая HTTP-ответ от Nexpose и затем возвращая либо XDocument, либо массив необработанных байтов, в зависимости от типа содержимого HTTP-ответа. После завершения метода ExecuteCommand() в листинге ниже мы сможем выполнить запрос API, а затем вернуть правильные данные ответа, в зависимости от типа содержимого ответа.
Обычно, когда Вы отправляете XML-команду в Nexpose, Вы получаете XML взамен. Но когда вы запрашиваете отчет о сканировании уязвимостей, например Отчет в формате PDF, который мы запросим после выполнения сканирования уязвимостей, Вы получите смешанный HTTP-ответ, а не ответ в формате application/xml. Непонятно, почему именно Nexpose изменяет ответ HTTP на основе отчетов в формате PDF, но поскольку наш запрос может возвращать ответ либо с отчетом в кодировке Base64, либо с XDocument (класс документа XML, который мы впервые использовали в разделе 3), нам необходимо определить, способен ли он обрабатывать оба типа ответов.
Чтобы начать чтение ответа HTTP от Nexpose, мы вызываем GetResponse() [1], чтобы мы могли прочитать поток ответа HTTP; затем мы создаем StreamReader [2] для чтения данных ответа в строку [3] и проверки ее типа содержимого. Если тип ответа — составной/смешанный [4], мы разбиваем ответ на массив строк, чтобы можно было проанализировать данные отчета, используя тот факт, что в составных/смешанных ответах Nexpose всегда используется строка —AxB9sl3299asdjvbA [5] для разделения параметров HTTP, в ответе HTTP.
После разделения HTTP-ответа третий элемент результирующего массива строк всегда будет содержать данные отчета в кодировке Base64, полученные при сканировании. Мы [6] используем две последовательности новой строки (\r\n\r\n) для разделения данных этого отчета. Теперь мы можем ссылаться только на данные в кодировке Base64, но сначала мы должны удалить некоторые недопустимые данные из конца отчета в кодировке Base64. Наконец, мы передаем данные в кодировке Base64 в Convert.FromBase64String() [7], который возвращает массив байтов данных, декодированных в Base64, которые затем можно записать в файловую систему в качестве окончательного отчета в формате PDF для последующего чтения.
Выход из системы и удаление нашей сессии
В листинге ниже показаны методы Logout() и Dispose(), которые упрощают выход из сеанса и очистку всех данных сеанса.
В Logout() [1] мы создаем XDocument [2] с корневым узлом LogoutRequest [3] и атрибутом session-id [4]. Когда мы отправляем эту информацию в Nexpose в формате XML, он попытается сделать недействительным токен идентификатора сеанса, фактически выведя нас из системы. В то же время мы устанавливаем для IsAuthenticated [5] значение false, а для SessionID — string.Empty, чтобы очистить старую информацию аутентификации; затем мы возвращаем XML-код ответа на выход из системы.
Мы будем использовать метод Dispose() [6] (требуемый интерфейсом IDisposable) для очистки нашего сеанса Nexpose. Как вы можете видеть в [7], мы проверяем, прошли ли мы аутентификацию, и если да, вызываем Logout(), чтобы сделать наш сеанс недействительным.
В листинге ниже показано, как мы будем использовать NexposeAPIVersion, чтобы определить, какую версию Nexpose API использовать.
Перечисление кода NexposeAPIVersion дает нам простой способ определить, к какому URI API следует отправлять HTTP-запросы. Мы использовали NexposeAPIVersion в листинге ниже, чтобы сделать именно это при создании URI API в ExecuteCommand().
Управление API Nexpose
В листинге ниже показано, как теперь мы можем использовать NexposeSession для связи с Nexpose API, а также для аутентификации и печати SessionID. Это хороший тест, позволяющий убедиться, что написанный нами код работает должным образом.
Мы [1] пытаемся пройти аутентификацию, передавая имя пользователя, пароль и IP-адрес сервера Nexpose в новый NexposeSession. Если аутентификация прошла успешно, мы отображаем SessionID, присвоенный сеансу, на экране. Если аутентификация не удалась, мы выдаем исключение с сообщением «Ошибка аутентификации».
На этом все. Всем хорошего дня!