Блог ·
Хибний секрет звучить як тиша
Три протоколи спостереження написано вручну замість того, щоб їх підтягнути, і одна відмова, яку сам протокол не дає відрізнити від мертвого сервера.
Автор: Provibr
Додати перевірку спостереження для нового протоколу виглядає як питання залежностей. Є крейт для MongoDB, крейт для BSON, крейт, що розмовляє мережевим протоколом SQL Server. Три рядки в маніфесті, сорок рядків клею, готово.
Усі три протоколи написано вручну, і причини варто записати, бо жодна з них не звучить як «вигадано не тут». Дві з них є вимірюваннями, а третя є обіцянкою, яку ми вже дали клієнтам про те, на що здатен цей агент.
Що залежність приносить насправді
Крейт BSON тягне за собою сорок один крейт. Це вимірюється і було виміряно, але вирішила не кількість.
Перевірка читає чотири поля з однієї відповіді сервера. Узагальнений тип документа тримає всю відповідь у памʼяті: перелік вузлів, теги, час останнього запису, час кластера, усе, що сервер захотів надіслати. Тоді фраза «ми читаємо звідти лише чотири поля» стає домовленістю між розробниками замість властивості коду, а домовленості порушує наступний раунд, якому з поважної причини потрібне пʼяте поле.
Тому існує читач, який проходить елементи, повертає чотири запитані і пропускає решту, ніде її не складаючи. Він не може втримати відповідь, отже обіцянка є формою, а не звичкою. Те саме рішення, що й перелік дозволеного для команди INFO в Redis, і те саме, що втримало повний драйвер бази даних поза перевіркою PostgreSQL: драйвер приносить із собою будівник запитів, і тоді в двійковому файлі, який працює в мережі клієнта, існує код, здатний поставити довільне питання його базі даних.
Крейт для SQL Server є гострішим випадком. Він збирається під нашу ціль випуску, і саме цього ми чекали як перешкоди. Приносить він другий стос TLS: лінію, що вийшла з підтримки, поруч із чинною, яку ми вже постачаємо. Загальнопроцесний криптопровайдер встановлюється для чинної лінії і для старої не робить нічого, тож TLS однієї перевірки використовував би інший провайдер, ніж будь-яке інше зʼєднання цього агента; вразливість у тій лінії є вразливістю, яку ми постачаємо і не можемо виправити, не чекаючи на постачальника; а політика сертифікатів, що живе рівно в одному модулі саме для того, щоб не розійтися, дістала б другу реалізацію на іншому інтерфейсі.
Пропуск є місцем, де саморобний читач помиляється
Хто пише власний читач для двійкового формату, може зробити помилку, яка важить більше за решту, і вона не в розборі. Вона в пропуску.
Кожне значення BSON має байт типу і довжину, що з нього випливає. Читач, який знає типи, що трапилися йому випадково, і пропускає невідомий тип на нуль байтів, не падає: він просто читає решту документа з хибного місця. Усе після цього зсунуте. Виходять правдоподібні значення, які не є надісланими.
Тому таблиця довжин є повною, із закритою відмовою для всього іншого. Невідомий байт типу є протокольною помилкою і ніколи не пропуском.
Та сама родина помилок сидить на рівень вище, у тому, як читаються числа. Сервер MongoDB відповідає ok: 1 як число з рухомою комою, а code помилки як 32-бітове ціле, в одному документі. Читач, що знає лише 32 біти, тихо губить перше, і тоді кожен невдалий вхід читається як успішний. Читач приймає число BSON у всіх трьох ширинах і повертає одне ціле.
Крок автентифікації, який мусить уміти впасти
Перевірка входить із SCRAM-SHA-256, а не з SCRAM-SHA-1, із двох причин, що вказують в один бік.
Старіший механізм подає в SCRAM не пароль. Він подає шістнадцятковий MD5 з імені користувача, сталого рядка і пароля. Підтримати його означало б написати код, що робить MD5 з облікових даних, заради механізму, який жоден сучасний сервер уже не мусить пропонувати.
Тихіша причина гірша. Сервер, що пропонує обидва, при відкоті опинився б на слабшому і ніщо не стало б червоним, і тоді зелене вимірювання стоїть над входом, слабшим, ніж клієнт вважає. Якщо сервер не пропонує SHA-256 для цього користувача, перевірка відмовляє власним кодом і каже про це.
Писати SCRAM руками в попередньому раунді для перевірки PostgreSQL відхилили, і заперечення було справжнім: обмін має підпис сервера, який клієнт має перевіряти, а реалізація, що пропускає цей крок, зелена проти справжнього сервера і так само зелена проти підробленого. Тут заперечення не обійшли, а відповіли на нього. Перевірка підпису є окремою функцією з власним кодом помилки, щоб «крок відсутній» не було тим самим, що «крок вдався», і є тест, який подає підроблений підпис і вимагає, щоб вхід не вдався. Той тест доведено червоним через прибирання перевірки.
Крейт, яким користується перевірка PostgreSQL, тут не годився, і це виміряно, а не припущено. Його реалізація SCRAM пише перше повідомлення клієнта з порожнім іменем користувача, бо PostgreSQL надсилає імʼя у своєму стартовому пакеті. MongoDB такого пакета не має і читає імʼя саме з цього поля. Вставити його потім означало б змінити ті самі байти, під якими вже підписано, і сервер відхилив би наш доказ. Інтерфейсу, щоб його передати, немає.
Відмова, яку протокол не дає назвати
Тепер частина, якої жодна заглушка не знайшла б.
RADIUS перевіряється запитом Status-Server, який питає сервер, чи він живий, і більше нічого. Очевидну альтернативу, запит доступу з тестовим обліковим записом, відкинуто: це справжня спроба входу в мережі клієнта, зі справжнім записом, яким хтось міг би скористатися, і вона пише рядок у його журнал автентифікації поміж його справжніми користувачами. За інтервалу в хвилину це тисяча чотириста сорок рядків на добу, які нічого не кажуть про його послугу, а в багатьох конфігураціях хибний пароль є приводом заблокувати запис, і тоді спостереження спричиняє саме той інцидент, який мало б побачити наперед.
Далі ми виміряли, проти справжнього сервера, що робить хибний спільний секрет.
Нічого. Сервер, який не може перевірити автентифікатор повідомлення в запиті, тихо відкидає пакет, без відповіді з помилкою. Це записано у специфікації, і це означає, що картина відмови від хибного секрета не відрізняється від правила брандмауера, хибного порту чи мертвої машини.
В адаптері цього не полагодити, бо це властивість протоколу. Що можна зробити, так це перестати вдавати. Повідомлення «немає відповіді» називає хибний спільний секрет як можливу причину, у тексті, який читає клієнт, щоб адміністратор не витрачав пів дня у брандмауері на пошук того, що лежить у файлі конфігурації. А якщо сервер усе ж відповідає, автентифікатор відповіді перевіряється, і розбіжність там повідомляється як факт автентифікації, а не як протокольна помилка, бо в такій конфігурації вона ним і є.
| Ситуація | Що бачить клієнт | Чому |
|---|---|---|
| Хибний секрет, немає відповіді | Немає відповіді, із секретом як можливою причиною | Сервер відкинув пакет; нічого не повернулося для огляду |
| Хибний секрет, відповідь приходить | Хибний секрет, як підсумок автентифікації | Автентифікатор відповіді не зійшовся |
| Правильний секрет, немає відповіді | Немає відповіді | Та сама тиша, і вона заслуговує тих самих слів |
Середній рядок є єдиним випадком, де різницю видно, і він не є звичним.
Сервер, який неможливо виміряти, сказано вголос
Останнє вимірювання сподобалося нам найменше.
SQL Server під Linux створює сертифікат при першому запуску. Той сертифікат є X.509 версії 1, формою без розширень, яка давно вже не призначена для TLS, і розбирач сертифікатів під ним її відхиляє. Тож шифрований режим падає на непідтримуваній версії сертифіката.
Варто бути точним щодо того, чому цей режим іще щось перевіряє. Особу там не зважують: імʼя в сертифікаті не перевіряється. Підпис зважують, бо без нього лишається не рукостискання, а обмін байтами з тим, хто відповів. Щоб зважити підпис, треба прочитати з сертифіката відкритий ключ, і саме цей крок відмовляє.
Отже, підсумок такий: SQL Server із власним створеним сертифікатом цією перевіркою виміряти неможливо. Клієнт читає справжню помилку розбирача як подробицю, процитовану, а не переказану, і засобом є справжній сертифікат у конфігурації сервера, що адміністратор, який робить свій сервер придатним до спостереження, робить і так.
Двох речей свідомо не зроблено. Перевірку підпису в тому режимі не пропускають, бо це зламало б обіцянку саме на тому налаштуванні, яке обирає більшість, і перетворило б видиму відмову на невидиму слабкість. І власного коду помилки для цього випадку ще немає, бо єдиним способом його розпізнати було б порівняння з людиночитним текстом бібліотеки, а це припущення про чужий код, яке може тихо зсунутися. Це варте окремого раунду з окремою пробою.
Немає й режиму зовсім без шифрування, з причини, властивої цьому протоколу: пароль у пакеті входу приховують сталим виключним «або» та обміном півбайтів. Це кодування, а не шифрування. Хто бачить байти, той читає пароль, тож нешифрований режим слав би пароль клієнта відкритим текстом його ж мережею, під назвою, яка натякає на інше.
Що знайшов канарок
Усе працювало проти справжніх серверів, а не заглушок: чотирнадцять перевірок проти MongoDB голого і з TLS, справжнього сервера RADIUS і справжнього SQL Server. Найкорисніший результат є нудним. Хибний пароль усюди є помилкою автентифікації і ніколи не є протокольною. Саме ця відмінність каже клієнтові, чи дивитися на облікові дані, чи на мережу, а попередній раунд помилився в ній у перевірці PostgreSQL: помилку під час кроку автентифікації там перекладали як «це не сервер PostgreSQL».
Що ми б записали
- Рахуйте, що залежність утримує, а не лише скільки вона коштує. Узагальнений тип документа перетворює «ми читаємо чотири поля» на домовленість замість властивості.
- Драйвер приносить із собою будівник запитів. Якщо обіцянка полягає в тому, що жодного довільного запиту надіслати не можна, коду для його надсилання в двійковому файлі бути не повинно.
- Два стоси TLS в одному двійковому файлі є питанням політики, а не розміру. Той, який ви не обирали, є тим, який ви не полагодите.
- У саморобному читачі таблиця пропусків є небезпечною частиною. Невідомий тип мусить бути помилкою; пропуск на нуль байтів зсуває все, що йде далі.
- Приймайте число в кожній ширині, яку дозволяє протокол. Одного поля, що приходить із рухомою комою, досить, щоб читати кожну невдачу як успіх.
- Відкіт до слабшого механізму має бути відмовою, а не відкотом. Зелене над слабшим входом гірше за червоне.
- Якщо протокол робить відмову невідрізненною, скажіть про це в повідомленні. Формулювання є єдиним місцем, де ця відмінність іще можлива.
- Деякі сервери виміряти неможливо, і це належить документації, а не обхідному шляху. Обхідним шляхом тут було б перестати перевіряти підписи.
Для ясності про те, чим це є: три перевірки побудовано, випробувано проти справжніх серверів і поставлено за перемикачем розгортання. Недоведеним лишається їхній прохід утрьох по дроту до справжнього агента клієнта, облік RADIUS поза запитом стану і SQL Server у конфігураціях, відмінних від однієї копії на Linux.
Із платформної роботи над Provibr. Як той самий підхід із канарком виявив два дефекти в попередніх перевірках баз даних, описано в канарок, що працює проти справжніх серверів, а як облікові дані цих перевірок дістаються агента, описано в таємниця, що подорожує лише раз.