[Из песочницы] Обзор уязвимости в Winbox от Mikrotik. Или большой фейл
Всем доброго времени суток, наверняка многие уже слышали про недавнюю уязвимость в роутерах Mikrotik, позволяющую извлечь пароли всех пользователей. В этой статье я бы хотел подробно показать и разобрать суть данной уязвимости.
Весь материал предоставляется лишь в ознакомительных целях, поэтому кода, эксплуатирующего уязвимость, тут не будет. Если вам не интересно узнать о причинах и внутреннем устройстве той или иной уязвимости, можете не читать дальше.
Начнём
Первое, с чего стоит начать, это анализ трафика между клиентом Winbox и устройством
Winbox — приложение для ОС WIndows, которое в точности повторяет веб-интерфейс и предназначено для администрирования и конфигурирования устройства с Router OS на борту. Поддерживается 2 режима работы, по протоколу TCP и UDP
Перед началом стоит отключить шифрование трафика в Winbox. Делается это следующим образом: нужно включить галочку Tools → Advanced Mode. После этого интерфейс изменится следующим образом:
![bw8c3ntuyktl2j8u8aoulc1pdbq.png](https://habrastorage.org/webt/bw/8c/3n/bw8c3ntuyktl2j8u8aoulc1pdbq.png)
Снимаем галочку Secure Mode. Запускаем Wireshark и пробуем авторизоваться на устройстве:
![mq2whsxb2vdhjddbedi9etvpb7c.png](https://habrastorage.org/webt/mq/2w/hs/mq2whsxb2vdhjddbedi9etvpb7c.png)
Как можно заметить ниже, после авторизации идёт запрос фала list и затем его содержимое нам полностью передаётся, может показаться, что всё хорошо, но взглянем на самое начало этой сессии:
![ilmsfoqh_24blolrg0fuhdxhfhy.png](https://habrastorage.org/webt/il/ms/fo/ilmsfoqh_24blolrg0fuhdxhfhy.png)
В самом начале Winbox отправляет точно такой же пакет с запросом файла list:
![arukqunodb50p3a0f6xawc19cuo.png](https://habrastorage.org/webt/ar/uk/qu/arukqunodb50p3a0f6xawc19cuo.png)
Рассмотрим его структуру:
- 37010035 — размер пакета
- M2 — константа, обозначающая начало пакета
- 0500ff01 — переменная 0xff0005 в значении True
- 0600ff09 01 — переменная 0xff0006 в значении 1 (Номер передаваемого пакета)
- 0700ff09 07 — переменная 0xff0007 в значении 7 (Открыть файл в режиме чтения)
- 01000021 04 6с967374 — переменная 0×01000001 строка list размером 4 байта (Обычно данная переменная отвечает за название файла)
- 0200ff88 02… 00 — массив 0xff0002 размером 2 элемента
- 0100ff88 02… 00 — массив 0xff0001 размером 2 элемента
В результате реверса протокола, и соответствующих бинарных файлов на стороне клиента и сервера, удалось в большей степени восстановить и понять структуру протокола, по которому Winbox общается с устройством.
Типы полей (Название: Цифровое обозначение)
- u32: 0×08000000
- u32_array: 0×88000000
- string: 0×20000000
- string_array: 0xA0000000
- addr6: 0×18000000
- addr6_array: 0×98000000
- u64: 0×10000000
- u64_array: 0×90000000
- true: 0×00000000
- false: 0×01000000
- bool_array: 0×80000000
- message: 0×28000000
- message_array: 0xA8000000
- raw: 0×30000000
- raw_array: 0xB0000000
- u8: 0×09000000
- be32_array: 0×88000000
Типы ошибок (Название: Цифровое обозначение)
- 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: 0×01
- STATUS_ERROR: 0×02
Структура полей в пакете
В начале любого поля идёт его тип — 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 байта за назначение переменной
Дополнительно каждый пакет содержит:
- специальные маркеры для обозначения начала пакета
- размер пакета
- маркеты, отвечающие за контроль больших пакетов
Найденные константы
- 0xfe0001 — Содержит идентификатор сессии (1 байт)
- 0xff0006 — Номер отправляемого пакета (1 байт)
- 0xff0007 — Режим доступа к файлу (1 байт)
Режимы доступа к файлу
- 7 — открыть для чтения
- 1 — открыть для записи
- 6 — создание директории
- 4 — выполнить чтение
- 5 — удалить
Теперь зная, как устроен протокол, мы можем произвольно генерировать нужные нам пакеты и смотреть, как на них реагирует девайс.
На стороне устройства, за обработку пакетов отвечает исполняемый файл /nova/bin/mproxy. Так как названия функций не были сохранены, я назвал функцию, которая обрабатывает пакет и принимает решения о том что делать с файлом file_handler (). Взглянем на саму функцию:
![siwnbthrbta88hokr1qjmcsllvu.png](https://habrastorage.org/webt/si/wn/bt/siwnbthrbta88hokr1qjmcsllvu.png)
P.S. Код который нас будет интересовать отмечен стрелочками.
Шаг 1
При получении пакета на открытие файла для чтения, он начинает обработку с этого блока:
![7aizx48ztgn_s15p0kmtkv1avdu.png](https://habrastorage.org/webt/7a/iz/x4/7aizx48ztgn_s15p0kmtkv1avdu.png)
В самом начале из пакета, с помощью функции nv: message: get
Далее функция tokenize () разбивает полученную строку на отдельные части, используя в качестве разделителя символ »/».
Полученный массив строк передаётся в функцию path_filter (), которая проверяет полученный массив строк на наличие »…», и в случае ошибок возвращает ошибку ERROR_NOTALLOWED (0xFE0009)
![nv6aex2ifzlg9o2pqhjcevvw_3g.png](https://habrastorage.org/webt/nv/6a/ex/nv6aex2ifzlg9o2pqhjcevvw_3g.png)
P.S. ERROR_NOTALLOWED так же будет получен в ответе, если нет прав доступа к файлу
Если же всё нормально, то к началу названия файла конкатенируется путь, к директории webfig или pckg
Шаг 2
![08tjxvub-j4ymwlkx8vbyvibu48.png](https://habrastorage.org/webt/08/tj/xv/08tjxvub-j4ymwlkx8vbyvibu48.png)
Если всё прошло успешно, открывается файл и его дескриптор сохраняется в глобальный объект.
Если файл открыть не удалось, то в ответе мы получаем ошибку: cannot open source file.
![ebgzbqqw37zawbh6-1rad67ql4g.png](https://habrastorage.org/webt/eb/gz/bq/ebgzbqqw37zawbh6-1rad67ql4g.png)
Таким образом, чтобы получить содержимое файла, должно быть соблюдено 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 ():
![0rqjoeg644_tcta0gave71d1n-e.png](https://habrastorage.org/webt/0r/qj/oe/0rqjoeg644_tcta0gave71d1n-e.png)
По коду отлично видно, что действительно происходит поиск вхождения »… », в полученный массив строк. Но дальше самое интересное, я выделил красным этот фрагмент.
Суть этого кода в том, что: Если предыдущий элемент так же является »…», то проверка считается проваленной. В противном случае — считать, что всё хорошо.
Т.е. чтобы всё сработало, нужно просто чередовать »/./» и »/…/» чтобы успешно перемещаться по любым каталогам и спускаться на любой уровень ФС.
![w8v42ssnrykzph96vl6ngf1_muq.jpeg](https://habrastorage.org/webt/w8/v4/2s/w8v42ssnrykzph96vl6ngf1_muq.jpeg)
Давайте посмотрим, как разработчики Mikrotik это исправили:
![un05tmw-tylgkiewgfutcshjssw.png](https://habrastorage.org/webt/un/05/tm/un05tmw-tylgkiewgfutcshjssw.png)
![t9fljb3v3tdf0ikho7mrspaoy08.png](https://habrastorage.org/webt/t9/fl/jb/t9fljb3v3tdf0ikho7mrspaoy08.png)
Теперь выход из цикла проверки происходит при первом же обнаружении »… ». Правда мне не совсем понятно, зачем добавили проверку вхождения одной точки. А из-за изменения механизма активации пользователя devel, к сожалению, нет возможности посмотреть это в динамике.
Подведём итог
- Router OS без проблем обрабатывает входящие пакеты ещё до авторизации пользователя
- Из-за некорректного фильтра мы получаем доступ к любому файлу
Учитывая предыдущие пункты, мы без проблем можем: создавать, удалять, читать, и записывать файлы, а так же создавать произвольные директории
Так что не удивительно, что имея доступ на чтение любых файлов без авторизации, первым что было сделано, это чтение файла с паролями пользователей. Благо в сети предостаточно информации о том, где он расположен, и как извлечь из него данные.
Так же данная уязвимость может стать отличной заменой для известной ранее возможности активации режима разработчика, ведь перезагружать устройство, делать backup\restore файла конфигурации теперь не нужно.