#21 Gray Hat C#. Руководство для хакера по созданию и автоматизации инструментов безопасности. Автоматический фаззинг конечной точки SOАP на предмет уязвимостей SQL-инъекций.
Здравствуйте, дорогие друзья.
Теперь, когда строительные блоки фаззера WSDL созданы, мы можем приступить к разработке действительно интересного инструмента. Используя класс WSDL, мы можем взаимодействовать с данными в WSDL объектно-ориентированным способом, что значительно упрощает фаззинг конечной точки SOAP. Мы начнем с написания нового метода Main(), который принимает один аргумент (URL-адрес конечной точки SOAP), который можно создать в отдельном файле внутри собственного класса Fuzzer, как показано в листинге ниже.
Сначала мы объявляем пару статических переменных на уровне класса перед методом Main(). Эти переменные будут использоваться во всех методах, которые мы пишем. Первая переменная — это класс WSDL [1], а вторая хранит URL-адрес конечной точки SOAP [2].
В методе Main() мы присваиваем переменной _endpoint значение первого аргумента, переданного в фаззер [3]. Затем мы печатаем дружественное сообщение, предупреждающее пользователя о том, что мы собираемся получить WSDL для службы SOAP.
После сохранения URL-адреса конечной точки мы создаем новый HttpWebRequest для получения WSDL из службы SOAP, добавляя ?WSDL в конец URL-адреса конечной точки. Мы также создаем временный XmlDocument для хранения WSDL и передачи конструктору класса WSDL. Передавая поток ответа HTTP методу XmlDocument Load() [4], мы загружаем XML, возвращенный HTTP-запросом, в XML-документ. Затем мы передаем полученный XML-документ конструктору класса WSDL для создания нового объекта WSDL. Теперь мы можем перебирать каждую из служб конечной точки SOAP и фаззить службу. Цикл foreach перебирает объекты в свойстве Services класса WSDL и передает каждый сервис методу FuzzService(), который мы напишем в следующем разделе.
Фаззинг отдельных сервисов SOAP
Метод FuzzService() принимает SoapService в качестве аргумента, а затем определяет, нужно ли нам фаззить сервис, используя параметры SOAP или HTTP, как показано в листинге ниже.
После печати текущего сервиса мы приступим к фаззингу: мы перебираем каждый порт SOAP в свойстве сервиса Ports. Используя метод Single() языкового запроса (LINQ) [1], мы выбираем одну привязку SoapBinding, соответствующую текущему порту. Затем мы проверяем, является ли привязка простым HTTP или SOAP на основе XML. Если привязка является HTTP-привязкой [2], мы передаем ее методу FuzzHttpPort() для фаззинга. В противном случае мы предполагаем, что привязка является привязкой SOAP, и передаем ее методу FuzzSoapPort().
Теперь давайте реализуем метод FuzzHttpPort(). Два типа возможных портов HTTP при работе с SOAP — это GET и POST. Метод FuzzHttpPort() определяет, какая из HTTP-команд будет использоваться при отправке HTTP-запросов во время фаззинга, как показано в листинге ниже.
Метод FuzzHttpPort() очень прост. Он проверяет, равно ли свойство Verb SoapBinding GET или POST, а затем передает привязку соответствующему методу — FuzzHttpGetPort() или FuzzHttpPostPort() соответственно. Если свойство Verb не равно GET или POST, генерируется исключение, предупреждающее пользователя о том, что мы не знаем, как обрабатывать данный HTTP-глагол. Теперь, когда мы создали метод FuzzHttpPort(), мы реализуем метод FuzzHttpGetPort().
Создание URL-адреса для фаззинга
Оба метода фаззинга HTTP немного сложнее, чем предыдущие методы фаззера. Первая половина метода FuzzHttpGetPort(), представленная в листинге ниже, создает исходный URL-адрес для фаззинга.
Первое, что мы делаем в методе FuzzHttpGetPort(), — используем LINQ [1] для выбора типа порта из нашего класса WSDL, который соответствует текущей привязке SOAP. Затем мы перебираем свойство Operations текущей привязки, которое содержит информацию о каждой операции, которую мы можем вызвать, и о том, как вызвать данную операцию. Во время итерации мы печатаем, какую операцию мы будем фаззить. Затем мы создаем URL-адрес, который будем использовать для выполнения HTTP-запроса для данной операции, добавляя свойство Location текущей операции к переменной _endpoint, которую мы установили в самом начале метода Main() [2]. Мы выбираем текущую SoapOperation (не путать с SoapBindingOperation!) из свойства Operations порта Type с использованием метода LINQ Single(). Мы также выбираем SoapMessage, используемый в качестве входных данных для текущей операции, используя тот же метод LINQ, который сообщает нам, какую информацию текущая операция ожидает при вызове.
Получив информацию, необходимую для настройки URL-адреса GET, мы создаем словарь для хранения имен параметров HTTP и типов параметров, которые мы будем отправлять. Мы перебираем каждую из входных частей, используя цикл foreach. Во время итерации мы добавляем в словарь имя каждого параметра и тип, который в данном случае всегда будет строкой. После того, как мы сохраним все имена наших параметров и их соответствующие типы, сохраненные рядом друг с другом, мы можем создать исходный URL-адрес для фаззинга.
Для начала мы определяем логическое значение first [3], которое будем использовать, чтобы определить, является ли параметр, добавленный к URL-адресу операции, первым параметром. Это важно, поскольку первый параметр строки запроса всегда отделяется от базового URL-адреса вопросительным знаком (?), а последующие параметры отделяются амперсандом (&), поэтому нам нужно быть уверенными в различии. Затем мы создаем список Guid, который будет содержать уникальные значения, которые мы отправляем вместе с параметрами, чтобы мы могли ссылаться на них во второй половине метода FuzzHttpGetPort().
Далее мы перебираем словарь параметров, используя цикл foreach.
В этом цикле foreach сначала мы проверяем, является ли тип текущего параметра строкой. Если это строка, мы создаем новый Guid, который будет использоваться в качестве значения параметра; затем мы добавляем новый Guid в созданный нами список, чтобы иметь возможность ссылаться на него позже. Затем мы используем оператор +=x [4], чтобы добавить параметр и новое значение к текущему URL-адресу. Используя тернарную операцию [5], мы определяем, следует ли ставить перед параметром вопросительный знак или амперсанд. Вот как параметры строки запроса HTTP должны быть определены, в соответствии с протоколом HTTP. Если текущий параметр является первым параметром, перед ним ставится вопросительный знак. В противном случае перед ним ставится амперсанд. Наконец, мы устанавливаем для параметра значение false, чтобы к последующим параметрам добавлялся правильный разделительный символ.
Фаззинг созданного URL
После создания URL-адреса с параметрами строки запроса, мы можем выполнять HTTP-запросы, систематически заменяя значения параметров испорченными значениями, которые могут вызвать ошибку SQL на сервере, как показано в листинге ниже. Эта вторая половина кода завершает метод FuzzHttpGetPort().
Теперь, когда у нас есть полный URL-адрес, который мы будем фаззить, мы распечатываем его, чтобы пользователь мог его увидеть. Мы также объявляем целое число k, которое будет увеличиваться по мере перебора значений параметров в URL-адресе, чтобы отслеживать потенциально уязвимые параметры. Затем, используя цикл foreach, мы перебираем список Guid, который использовали в качестве значений для наших параметров. В цикле foreach первое, что мы делаем, — это заменяем текущий Guid в URL-адресе на строку «fd’sa» с помощью метода replace() [1], который должен испортить любые SQL-запросы, использующие это значение, без надлежащей очистки. Затем мы создаем новый HTTP-запрос с измененным URL-адресом и объявляем пустую строку с именем resp, которая будет содержать ответ HTTP.
В блоке try/catch мы пытаемся прочитать ответ на HTTP-запрос от сервера с помощью StreamReader [2]. Чтение ответа вызовет исключение, если сервер вернет ошибку 500 (что произойдет, если на стороне сервера возникнет исключение SQL). Если генерируется исключение, мы перехватываем его в блоке catch [3], и пытаемся снова прочитать ответ с сервера. Если ответ содержит синтаксическую ошибку строки, мы печатаем сообщение, предупреждающее пользователя о том, что текущий параметр HTTP может быть уязвим для SQL-инъекции. Чтобы точно сообщить пользователю, какой параметр может быть уязвимым, мы используем целое число k в качестве индекса списка Parts [4], и извлекаем имя текущего свойства. Когда все сказано и сделано, мы увеличиваем целое число k на 1 и возвращаемся к началу цикла foreach, с новым значением для проверки.
Это полный метод фаззинга портов HTTP GET SOAP. Далее нам нужно реализовать FuzzHttpPostPort() для фаззинга портов POST SOAP.
На этом все. Всем хорошего дня!