Огляд уразливості в 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 елемента
Читайте також  PeerTube 1.0: відеохостинг без дата-центру та капітальних витрат

В результаті реверсу протоколу, та відповідних бінарних файлів на стороні клієнта і сервера, вдалося більшою мірою відновити і зрозуміти структуру протоколу, за яким 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 байта, старший байт відповідає за значення (TrueFalse), молодші 3 байти за призначення змінної

Додатково за кожний пакет містить:

  1. спеціальні маркери для позначення початку пакета
  2. розмір пакета
  3. маркети, відповідальні за контроль великих пакетів
Читайте також  [ Психологія дизайну ] — The Psychology of Design

Знайдені константи

  • 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. З-за некоректної фільтра ми отримуємо доступ до будь-якого файлу

Враховуючи попередні пункти, ми без проблем можемо: створювати, видаляти, читати і записувати файли, а так само створювати довільні директорії

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

Так само дана уразливість може стати відмінною заміною для відомої раніше можливості активації режиму розробника, адже перезавантажувати пристрій, робити backuprestore файлу конфігурації тепер не потрібно.

Степан Лютий

Обожнюю технології в сучасному світі. Хоча частенько і замислююся над тим, як далеко вони нас заведуть. Не те, щоб я прям і знаюся на ядрах, пікселях, коллайдерах і інших парсеках. Просто приходжу в захват від того, що може в творчому пориві вигадати людський розум.

Вам також сподобається...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *