Главная » Хабрахабр » [Из песочницы] Обзор уязвимости в Winbox от Mikrotik. Или большой фейл

[Из песочницы] Обзор уязвимости в Winbox от Mikrotik. Или большой фейл

Всем доброго времени суток, наверняка многие уже слышали про недавнюю уязвимость в роутерах Mikrotik, позволяющую извлечь пароли всех пользователей. В этой статье я бы хотел подробно показать и разобрать суть данной уязвимости.

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

Начнём

Первое, с чего стоит начать, это анализ трафика между клиентом Winbox и устройством

Поддерживается 2 режима работы, по протоколу TCP и UDP Winbox — приложение для ОС WIndows, которое в точности повторяет веб-интерфейс и предназначено для администрирования и конфигурирования устройства с Router OS на борту.

Перед началом стоит отключить шифрование трафика в Winbox. Делается это следующим образом: нужно включить галочку Tools -> Advanced Mode. После этого интерфейс изменится следующим образом:

Снимаем галочку Secure Mode. Запускаем Wireshark и пробуем авторизоваться на устройстве:

Как можно заметить ниже, после авторизации идёт запрос файла list и затем его содержимое нам полностью передаётся, может показаться, что всё хорошо, но взглянем на самое начало этой сессии:

В самом начале Winbox отправляет точно такой же пакет с запросом файла list:

Рассмотрим его структуру:

  1. 37010035 — размер пакета
  2. M2 — константа, обозначающая начало пакета
  3. 0500ff01 — переменная 0xff0005 в значении True
  4. 0600ff09 01 — переменная 0xff0006 в значении 1 (Номер передаваемого пакета)
  5. 0700ff09 07 — переменная 0xff0007 в значении 7 (Открыть файл в режиме чтения)
  6. 01000021 04 6с967374 — переменная 0x01000001 строка list размером 4 байта (Обычно данная переменная отвечает за название файла)
  7. 0200ff88 02… 00 — массив 0xff0002 размером 2 элемента
  8. 0100ff88 02… 00 — массив 0xff0001 размером 2 элемента

В результате реверса протокола, и соответствующих бинарных файлов на стороне клиента и сервера, удалось в большей степени восстановить и понять структуру протокола, по которому Winbox общается с устройством.

Описание протокола NvMessage

Типы полей (Название: Цифровое обозначение)

  • u32: 0x08000000
  • u32_array: 0x88000000
  • string: 0x20000000
  • string_array: 0xA0000000
  • addr6: 0x18000000
  • addr6_array: 0x98000000
  • u64: 0x10000000
  • u64_array: 0x90000000
  • true: 0x00000000
  • false: 0x01000000
  • bool_array: 0x80000000
  • message: 0x28000000
  • message_array: 0xA8000000
  • raw: 0x30000000
  • raw_array: 0xB0000000
  • u8: 0x09000000
  • be32_array: 0x88000000

Типы ошибок (Название: Цифровое обозначение)

  • SYS_TO: 0xFF0001
  • STD_UNDOID: 0xFE0006
  • STD_DESCR: 0xFE0009
  • STD_FINISHED: 0xFE000B
  • STD_DYNAMIC: 0xFE0007
  • STD_INACTIVE: 0xFE0008
  • STD_GETALLID: 0xFE0003
  • STD_GETALLNO: 0xFE0004
  • STD_NEXTID: 0xFE0005
  • STD_ID: 0xFE0001
  • STD_OBJS: 0xFE0002
  • SYS_ERRNO: 0xFF0008
  • SYS_POLICY: 0xFF000B
  • SYS_CTRL_ARG: 0xFF000F
  • SYS_RADDR6: 0xFF0013
  • SYS_CTRL: 0xFF000D
  • SYS_ERRSTR: 0xFF0009
  • SYS_USER: 0xFF000A
  • SYS_STATUS: 0xFF0004
  • SYS_FROM: 0xFF0002
  • SYS_TYPE: 0xFF0003
  • SYS_REQID: 0xFF0006

Значения ошибок (Название: Цифровое обозначение)

  • ERROR_FAILED: 0xFE0006
  • ERROR_TOOBIG: 0xFE000A
  • ERROR_EXISTS: 0xFE0007
  • ERROR_NOTALLOWED: 0xFE0009
  • ERROR_BUSY: 0xFE000C
  • ERROR_UNKNOWN: 0xFE0001
  • ERROR_BRKPATH: 0xFE0002
  • ERROR_UNKNOWNID: 0xFE0004
  • ERROR_UNKNOWNNEXTID: 0xFE000B
  • ERROR_TIMEOUT: 0xFE000D
  • ERROR_TOOMUCH: 0xFE000E
  • ERROR_NOTIMP: 0xFE0003
  • ERROR_MISSING: 0xFE0005
  • STATUS_OK: 0x01
  • STATUS_ERROR: 0x02

Структура полей в пакете

В начале любого поля идёт его тип — 4 байта (3 байта — назначение переменной, об этом позже, 1 байт — непосредственно тип этой переменной) затем длина 1-2 байта и непосредственно значение.

Массивы

Образно массив можно описать следующей структурой:

struct Array { uint32 type; uint8 count; uint32 item1; uint32 item2; ... uint8 zero;
}

Тип (4 байта) / Кол-во элементов (1 байт) / Элементы (4 байта) / В завершении байт \x00

Строки

Строки не нуль-терминированны, а имеют четко заданную длину:

struct Array { uint32 type; uint8 length; char text[length];
}

Числа

Самый простой тип в пакете, его можно представить как тип-значение:

struct Array { uint32 type; uint8/32/64 value;
}

В зависимости от типа, значение имеет соответствующую размерность бит.

Булевый тип

Размер поля 4 байта, старший байт отвечает за значение (True\False), младшие 3 байта за назначение переменной

Дополнительно каждый пакет содержит:

  1. специальные маркеры для обозначения начала пакета
  2. размер пакета
  3. маркеты, отвечающие за контроль больших пакетов

Найденные константы

  • 0xfe0001 — Содержит идентификатор сессии (1 байт)
  • 0xff0006 — Номер отправляемого пакета (1 байт)
  • 0xff0007 — Режим доступа к файлу (1 байт)

Режимы доступа к файлу

  • 7 — открыть для чтения
  • 1 — открыть для записи
  • 6 — создание директории
  • 4 — выполнить чтение
  • 5 — удалить

Теперь зная, как устроен протокол, мы можем произвольно генерировать нужные нам пакеты и смотреть, как на них реагирует девайс.

Так как названия функций не были сохранены, я назвал функцию, которая обрабатывает пакет и принимает решения о том что делать с файлом file_handler(). На стороне устройства, за обработку пакетов отвечает исполняемый файл /nova/bin/mproxy. Взглянем на саму функцию:

S. P. Код который нас будет интересовать отмечен стрелочками.

Шаг 1

При получении пакета на открытие файла для чтения, он начинает обработку с этого блока:

В самом начале из пакета, с помощью функции nv::message::get<nv::string_id>() извлекается название файла.

Далее функция tokenize() разбивает полученную строку на отдельные части, используя в качестве разделителя символ "/".

Полученный массив строк передаётся в функцию path_filter(), которая проверяет полученный массив строк на наличие "..", и в случае ошибок возвращает ошибку ERROR_NOTALLOWED (0xFE0009)

S. P. ERROR_NOTALLOWED так же будет получен в ответе, если нет прав доступа к файлу

Если же всё нормально, то к началу названия файла конкатенируется путь, к директории webfig или pckg

Шаг 2

Если всё прошло успешно, открывается файл и его дескриптор сохраняется в глобальный объект.

Если файл открыть не удалось, то в ответе мы получаем ошибку: cannot open source file.

Таким образом, чтобы получить содержимое файла, должно быть соблюдено 3 условия:

  1. Путь к файлу не содержит "..";
  2. Имеются права на доступ к файлу;
  3. Файл существует и может быть успешно открыт.

Теперь давайте попробуем отправить несколько пакетов для проверки работоспособности этой функции:

$ ./untitled.py -t 192.168.88.1 -f /etc/passwd
Error: SYS_ERRNO => ERROR_FAILED
Error: SYS_ERRSTR => cannot open source file $ ./untitled.py -t 192.168.88.1 -f /../../../etc/passwd
Error: SYS_ERRNO => ERROR_NOTALLOWED $ ./untitled.py -t 192.168.88.1 -f //./././././../etc/passwd
Error: SYS_ERRNO => ERROR_FAILED
Error: SYS_ERRSTR => cannot open source file

Так! А вот это уже странно… Мы помним, что ERROR_NOTALLOWED появляется если не прошла проверка в path_filter(), иначе мы бы ещё получили сообщение об отсутствии прав доступа, но в последнем случае, получается, что поиск файла производился в директории верхнего уровня?

Попробуем такой способ:

$ ./untitled.py -t 192.168.88.1 -f //./.././.././../etc/passwd
xvM2����� � 1Enobody:*:99:99:nobody:/tmp:/bin/sh
root::0:0:root:/home/root:/bin/sh

И это сработало. Но почему? Давайте взглянем на код функции path_filter():

По коду отлично видно, что действительно происходит поиск вхождения ".. ", в полученный массив строк. Но дальше самое интересное, я выделил красным этот фрагмент.
Суть этого кода в том, что: Если предыдущий элемент так же является "..", то проверка считается проваленной. В противном случае — считать, что всё хорошо.

чтобы всё сработало, нужно просто чередовать "/./" и "/../" чтобы успешно перемещаться по любым каталогам и спускаться на любой уровень ФС. Т.е.

Давайте посмотрим, как разработчики Mikrotik это исправили:

Сравнение псевдо-С кода

Теперь выход из цикла проверки происходит при первом же обнаружении ".. ". Правда мне не совсем понятно, зачем добавили проверку вхождения одной точки. А из-за изменения механизма активации пользователя devel, к сожалению, нет возможности посмотреть это в динамике.

Подведём итог

  1. Router OS без проблем обрабатывает входящие пакеты ещё до авторизации пользователя
  2. Из-за некорректного фильтра мы получаем доступ к любому файлу

Учитывая предыдущие пункты, мы без проблем можем: создавать, удалять, читать, и записывать файлы, а так же создавать произвольные директории

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

Так же данная уязвимость может стать отличной заменой для известной ранее возможности активации режима разработчика, ведь перезагружать устройство, делать backup\restore файла конфигурации теперь не нужно.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

Сетевой дайджест: 20 экспертных материалов о протоколах, стандартах и информационной безопасности

В эту подборку мы включили свежие посты, подготовленные специалистами компании VAS Experts. Главные темы подборки — сетевые протоколы, 5G и информационная безопасность. Под катом вы также найдете ряд рекомендаций по построению сетей операторов связи. / Pxhere / PD Про ИБ ...

[Перевод] Забудьте о мегаструктурах инопланетян: новые наблюдения объясняют поведение звезды Табби одной только пылью

Художественное изображение KIC 8462852, яркость которой за последние несколько лет менялась необычным образом Когда планета проходит перед её родительской звездой, если смотреть с нашей точки зрения, часть света звезды на некоторое время исчезает. Научная охота за планетами в XXI веке ...