Обзор уязвимости в Winbox от Mikrotik. Или большой фейл

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

Начнём


Первое, с чего стоит начать, это анализ трафика между клиентом Winbox и устройством
Winbox — приложение для ОС WIndows, которое в точности повторяет веб-интерфейс и предназначено для администрирования и конфигурирования устройства с Router OS на борту. Поддерживается 2 режима работы, по протоколу TCP и UDP
Перед началом стоит отключить шифрование трафика в 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 String {
    uint32 type;
    uint8 length;
    char text[length];
}

Числа


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

struct u* {
    uint32 type;
    uint8/32/64 value;
}

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

Булевый тип


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

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

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


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


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

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

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


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

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


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

Шаг 1


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


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

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

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


P.S. 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 файла конфигурации теперь не нужно.
Поделиться публикацией
Ой, у вас баннер убежал!

Ну. И что?
Реклама
Комментарии 27
  • 0
    известной ранее возможности активации режима разработчика

    А можно поподробнее?
    • 0
      Тут Можете почитать подробнее, и не только об этом
      • 0
        Спасибо, заценю
        • 0
          В 6.42 трюк с получением shell не сработал, видимо прикрыли. Эх а было интересно…
          • 0
            Там директорию надо теперь в другом месте создавать. Чтобы она как пакет воспринималась. Но после патча и этой баги, даже такое уже не возможно.
            • 0
              Ясно, ну будем ждать утечку devel.npk, видимо у них он есть
              • 0
                Или ключей, чтобы его самим собрать и подписать
      • 0
        Хм. Интересненько! Так они уже выпустили фикс, если да, то как давно? Или большая часть устройств так и находится с этой неприятной дыркой?
        • 0
          Это уже пропатчили
          • 0
            пропатчили.
            Да, множество устройств с дырой.
            • 0
              Насколько я понимаю, именно об этом микротик делал рассылку 28го марта по всем, у кого есть аккаунты на сайте микрота.

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

              Если в течении суток не отпишутся нигде, сам заведу тему у них на форуме))
              • 0
                То о чем вы говорите это совсем другая история Chimay-Red
                • 0
                  Понял, да, но я не вижу, чтобы это фиксилось.
                  К сожалению, нет сейчас возможности протестировать на уязвимости самые новые прошивки, но уязвимости больше года…

                  Про Chimay-Red они выпустили отдельный пресс-релиз, а тут — тишина…
          • 0

            Не понял. А что, кто-то до сих пор хранит пароли пользователей, а не только солёные хэши?
            Впрочнм, при наличии доступа и на запись это не очень важно...

            • 0
              С паролями в принципе два варианта PAP (Password Authentication Protocol) и CHAP (Challenge Handshake Authentication Protocol): по первому вы храните хэш, но передаёте пароль; по второму вы передаёте хэш, но храните пароль.
            • 0
              А скрипт из статьи где-то выложен?
              • 0
                Да, скрипт не помешал бы. Все-равно уязвимость уже закрыта
                • 0
                  Рабочий эксплойт для этой уязвимости, добывающий логин с паролем, имеется в Router Scan, ещё с апреля месяца.
                  • 0
                    Спасибо! Я и забыл за этот инструмент
                • 0
                  В сети уже есть примитивный PoC. Протокол общения я описал, даже есть пример как это выглядит в пакете. Так что не думаю что могут возникнуть сложности это всё повторить.

                  Возможно в будущем будет опубликована библиотека с реализацией этого протокола. Но пока так.
                  • 0
                    В сети уже есть примитивный PoC
                    Можно ссылку на PoC?
                • +1
                  Я ещё в мае писал статью habr.com/post/359038
                  Но на некоторых ресурсах особо упёртые «админы» мне пытались доказать, что эта бага закрыта год назад.
                  И судя по собранной мною статистике ещё сотни тысяч устройств никак не защищены.
                  Лидер Бразилия, а за ней Россия. На третьем месте Индонезия.
                  • 0

                    Будет наукой для админов. Во внешний мир должен смотреть только ssh и то на левом порту. А лучше с порт кнокингом.
                    Все уязвимости говорят только о популярности микротика. Ведь раньше он никому не был интересен и никто его так не ковырял.

                    • 0
                      По мимо того, что RouterOS умеет дофиги без дополнительных лицензий, да и стоимость обучения ощутимо ниже, чем у той же Cisco (от 22к рублей MTCNA).
                    • 0

                      На этом MTCNA довольно хорошо рассматривают фаервол. Но MTCNA сильно отличается от CCNA, так что их цены и материал обучения сравнивать не стоит.

                      • +1
                        Спасибо автору за статью, вот PoC скрипта для получения файла passwd:
                        PoC
                        Тестил на RouterOS 6.39_x86 VMWare

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

                        Самое читаемое