March 20, 2019

Создание простейшего фаззера для протокола MQTT

Обучайтесь с нами! Мы в Telegram: https://t.me/hackyouhack
1. Группа Вконтакте: https://vk.com/hackyouhack
2. YouTube Канал: http://clck.ru/EFngv
3. Blogger: https://hackyouhack1.blogspot.com/

В этой статье показано, насколько просто создать фаззер для протокола MQTT при помощи фреймворка Polymorph.

Предполагается, что читатель знаком с протоколом MQTT. В противном случае рекомендую ознакомиться с сайтом http://mqtt.org/. Вначале настроим рабочую среду для фаззинга, которая будет работать на базе Kali Linux со следующими зависимостями:

Polymorph framework

apt-get install build-essential python-dev libnetfilter-queue-dev tshark tcpdump python3-pip wireshark

pip3 install --process-dependency-links polymorph

Mosquitto

apt-get install mosquitto mosquitto-clients

Radamsa

sudo apt-get install gcc make git wget

git clone https://github.com/aoh/radamsa.git&& cd radamsa && make && sudo make install

После того как все вышеперечисленные пакеты установлены, прежде, чем конструировать фаззер, необходимо протестировать брокер сообщений mosquitto, установленный на локальном хосте. Мы будем использовать два терминала. Первый клиент будет подписываться на определенную тему, второй – будет публиковать сообщение в той же самой теме. На рисунке ниже показаны результаты выполнения команд.

После того как коммуникация между клиентами налажена, мы воспользуемся фреймворком Polymorph для перехвата и модификации MQTT-пакетов в режиме реального времени. Конкретно в нашем случае, мы будем работать с полем msg в пакетах MQTTPublish. Модификация других полей происходит тем же самым образом. Кроме того, с целью упрощения задачи, мы будем изменять пакеты, передаваемые через протокол IPv4. В случае, если вам будут попадаться MQTT-пакеты, передаваемые через IPv6, вы можете отключить использование этого протокола при помощи следующей команды к интерфейсу loopback:

sudo sh -c 'echo 1 > /proc/sys/net/ipv6/conf/loopback/disable_ipv6'

Чтобы получить доступ к инструментам фреймворка Polymorph, достаточно ввести в терминале следующую команду:

С этого момента можно приступать к конструированию фаззера. Поскольку оба клиента находятся в одном месте, и нам не нужно перехватывать коммуникацию между двумя машинами, спуфинг использоваться не будет. Соответственно, для начала «прослушивания» достаточно команды capture.

На данный момент наша задача - перехват одного из пакетов, который мы хотим модифицировать. После перехвата фреймворк преобразует пакет в шаблон, с которым мы в дальнейшем будем работать. Во время сниффинга необходимо отсылать сообщение MQTTPublish другому клиенту.

После пересылки нажимаем Ctrl-C для завершения сниффинга и вводим команду show для просмотра перехваченных пакетов на экране.

Как видно на рисунке выше, у большинства пакетов присутствует слой Raw. Сей факт означает, что, скорее всего, при помощи основных инструментов не удалось перехватить и корректно проанализировать нужные нам пакеты. При помощи команды dissect мы будем использовать более продвинутые анализаторы, которые позволяют посмотреть скрытые части пакетов.

Теперь, когда у нас появилось более четкое понимание о назначении каждого байта в перехваченных пакетах, нужно выбрать шаблон (template), соответствующий тому пакету, который мы хотим модифицировать. Для получения более детальной информации можно воспользоваться командой wireshark, чтобы открыть одноименное приложение, а для доступа к выбранному шаблону – командой template.

Теперь мы находимся в контексте выбранного шаблона. Далее при помощи команды show можно посмотреть различные слои, поля и типы, используемые в шаблоне. Концепция шаблона – наиболее важная абстракция фреймворка Polymorph, который позволяет получить доступ к перехваченным пакетам в режиме реального времени при помощи простейшего кода, используемого для обработки пакетов. Более того, при сохранении сессии все условные функции и структуры фреймворка сохраняются в специальном хранилище.

Затем выполнение переходит к условным функциям, которые подразделяются на три категории: предусловные (precondition), постусловные (postcondition) и выполняемые (execution). Когда пользователь вводит команду intercept в контексте текущего шаблона, на машине, где находится фреймворк Polymorph, перенаправление пакетов приостанавливается на уровне ядра и начинается пересылка утилите, которая осуществляет обработку. Для каждого перехваченного пакета будут выполняться условные функции, определяемые пользователем.

Рассмотрим пример того, как работают условные функции. Добавляем предусловную функцию для текущего шаблона при помощи команды precs -a test_condition.

Если вы используете редактор pico, не путайте пробелы и табуляции. Для выравнивания кода лучше использовать пробелы. Вы также можете указать редактор из переменной PATH при помощи опции –e:

def test_precondition(packet): 

print("The next packet arrive:") 

print(packet.raw) 

returnpacket

Введите «y» для сохранения кода на диске, а затем введите команду precs -s для просмотра функции, добавленной в качестве предусловия.

Далее вводим команду intercept:

Теперь мы можем в режиме реального времени наблюдать передачу и обработку пакетов, а также работу функции, которую мы добавили в качестве предусловия. Всю схему можно протестировать, если отправить сообщение MQTTPublish от MQTT-клиента к брокеру сообщений.

Условные функции – еще одна важная абстракция фреймворка, которая работает следующим образом. Когда пакет перехватывается в режиме реального времени, условные функции, заданные пользователем, выполняются в определенном порядке. Вначале выполняются предусловные функции из секции preconditions в том порядке, в котором добавил пользователь. Затем очередь доходит до выполняемых функции из секции executions. В самом конце выполняются постусловные функции из секции postconditions. Если в любой момент любая функция вернула значение None, выполняемая цепочка прерывается, и пакет отправляется в текущем состоянии. С другой стороны, если возвращается пакет, полученный в качестве аргумента, выполнение цепочки условных функций продолжается. Следует помнить, что пакет, полученный в качестве аргумента, представляет собой пакет, перехваченный в режиме реального времени в текущий момент.

После того как мы уяснили логику работы условных функций, выходим из режима перехвата, нажав комбинацию клавиш Ctrl-C, после чего система будет просто перенаправлять пакеты и не будет осуществлять отсылку на обработку фреймворку Polymorph. Теперь добавим несколько условных функций в секции preconditions, executions и postconditions, которые будут запускаться для каждого перехваченного пакета. Чтобы удалить текущую условную функцию, которую мы добавили, воспользуйтесь командой precs -d test_condition.

Preconditions

В секцию preconditions будут добавлены две функции:

precs -a global_vars -e editor 

precs -a filter_mqttpublish -e editor

Первая предусловная функция global_vars создает глобальную переменную, которая будет оставаться константой для всех перехваченных пакетов. В этой переменной будут храниться тестовые сценарии, используемые для обработки пакетов MQTTPublish.

def global_vars(packet): 

try: 

packet.fuzz_cases 

except: 

setattr(packet, 'fuzz_cases', []) 

return packet

Вторая предуслованя функция filter_mqttpublish будет фильтровать входящие пакеты таким образом, что вся последующая цепочка условных функций будет выполняться только в том случае, если в поле msgtype находится значение 48. Обратите внимание, что благодаря использованию абстракции на базе шаблона, фреймворк Polymorph знает позицию поля msgtype среди байтов перехваченного пакета и, соответственно, может легко получить доступ к нужному байту.

def filter_mqttpublish(packet): 

try: 

if packet['RAW.MQTT']['msgtype'] == 48: 

return packet 

except: 

return None

Executions

Функция в секции executions чуть сложнее, чем две предыдущих, но в целом остается все такой же простой. Код, показанный ниже, выполняет следующие задачи:

  1. Конвертирует перехваченный пакет в формат приложения Scapy, чтобы упростить работу с полями протокола MQTT. Особенно при работе с закодированными размерами полей. Некоторое время назад я написал MQTT-спецификациюдля Scapy.
  2. Проверяет, находятся ли обработанные значения в списке тестовых сценариев. Список хранится в глобальной переменной, созданной ранее. Если список пустой, запускается функция Radamsa для генерирования новых тестовых сценариев, которые также будут хранится в глобальной переменной.
  3. Использует Scapy для вставки тестового значения в поле msg пакета и удаляет это значение из списка (чтобы не вставлять дважды). Кроме того, производится пересчет контрольных полей, таких как lengths и chksums.

def insert_value(packet): 

import subprocess 

from os import listdir 

from os.path import join 

from scapy.all import IP 

from scapy.contrib.mqtt import MQTT 

# Building a Scapy packet 

pkt = IP(packet.raw[14: ]) 

# Retrieving the fuzzing case 

if not packet.fuzz_cases: 

valid_cases = "valid_cases" 

dpath = "fuzz_cases" 

subprocess.check_call(["radamsa", 

"-o", 

join(dpath, "fuzz-%n.%s"), 

"-n", 

"58", 

"-r", 

valid_cases]) 

packet.fuzz_cases = [open(join(dpath, f), 'rb').read() 

for f in listdir(dpath)] 

# Inserting the value and recalculating some fields 

del pkt['IP'].len 

del pkt['IP'].chksum 

del pkt['TCP'].chksum 

del pkt['MQTT'].len 

del pkt['MQTTPublish'].length 

pkt['MQTTPublish'].topic = packet.fuzz_cases.pop() 

pkt.show2() 

packet.raw = bytes(pkt) 

return packet

Теперь у нас есть все для того, чтобы создать фаззер на базе фреймворка Polymorph для протокола MQTT. Чтобы запустить нашу конструкцию, нужно создать две директории, на которые должна ссылаться переменная PATH и которые будут использоваться фреймворком Polymorph. Первая папка - valid_cases, вторая - fuzz_cases. Эти директории будут использоваться функцией Radamsa для чтения тестовых сценариев и преобразования в такие сценарии, которые могут быть пригодны для эксплуатации уязвимости. Новые сценарии добавляются, как показано на рисунке ниже:

Как только сценарии добавлены, переключаемся в контекст шаблона и запускаем команду intercept. Затем при помощи MQTT-клиента отсылаем сообщение, которое, по возможности, должны быть длинными, чтобы не возникло проблем с порядковыми номерами в сессиях, используемых в стеке протоколов TCP/IP.

Мы можем отслеживать изменения пакетов и появление значений, отсылаемых функцией Radamsa, в режиме реального времени. Теперь нужно организовать простейший цикл в Bash, внутри которого будут проверяться все тестовые значения. Кроме того, мы можем запустить тестирование внутри отладчика для сбора и последующего анализа возникающих исключений.

#!/bin/bash 

while true; do 

mosquitto_pub -t `python3 -c "print('A'*10000)"` -m 'hello' 

done

Команда save, запущенная из контекста шаблона, позволяет экспортировать шаблон. Последующий импорт во фреймворк Polymorph на другой машине выполняется при помощи команды polymorph -t template.json. Таким образом, вы сможете делиться шаблонами со своими коллегами. Мой шаблон можно скачать, используя ссылку ниже:

https://gist.github.com/shramos/2b98867d2c344b36bfee6a7c799fbb8f

Обучайтесь с нами! Мы в Telegram: https://t.me/hackyouhack
1. Группа Вконтакте: https://vk.com/hackyouhack
2. YouTube Канал: http://clck.ru/EFngv
3. Blogger: https://hackyouhack1.blogspot.com/