Перейти до вмісту

Блог ·

Чому сканер вразливостей має слати нуль байтів

Межу між перевіркою та атакою становлять байти, які ви кладете на дріт. Як перетворити цю обіцянку на число, на якому тести падають, і самі виміри.

Автор: Provibr

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

Те, що відрізняє це від вторгнення, не є вашим наміром, і це не галочка у ваших умовах обслуговування. Це те, що насправді вийшло із сокета. Усе інше є оповіддю про те, що вийшло із сокета.

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

Обіцянка в коментарі є обіцянкою з терміном придатності

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

Хтось додає EHLO до проби SMTP, щоб вона могла виявити, чи пропонується STARTTLS, а це справді корисна інформація. Наявні тести й далі проходять, бо вони перевіряють те, що проба знайшла, а не те, що вона сказала. Від того дня програма надсилає протокольні команди на машини клієнтів, а проєктний документ і далі каже, що не надсилає. Ніщо не почервоніло. Ніщо ніколи й не почервоніє.

Тож ми зробили з цього число там, де число можливе. Кожен сокет, який відкриває перевірка, загорнуто в потік, що рахує записані байти, і підсумок їде назад разом із результатом. Для чотирьох із шести видів проби тести перевіряють точну цифру: нуль для двох, які не надсилають нічого, тридцять два для SSH, вісім для PostgreSQL. Інші дві не можуть її мати, бо client hello і SMB negotiate різняться довжиною залежно від того, що пропонується всередині; їхньою межею натомість є форма: одне повідомлення і нічого після нього. Додавання EHLO до проби, перевіреної на нулі, тепер є червоним тестом із числом усередині.

Дві деталі в тому лічильнику виявилися важливими.

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

Скільки коштує кожна проба

ПробаЩо ми надсилаємоЩо читається
Лише відкриттянічого, перевірено на 0 байтівнічого
Банер: FTP, Telnet, SMTP, MySQLнічого, перевірено на 0 байтіводне обмежене привітання
SSHодин рядок ідентифікації, перевірено на 32 байтирядок ідентифікації сервера, а потім його список алгоритмів, якщо він його віддає
TLSодин client hello, довжина різнапривітання сервера й сертифікат
SMBодин negotiate, довжина різнавідповідь на negotiate
PostgreSQLзапит SSL, перевірено на 8 байтіводин байт

За відкриттям не йде нічого. Жодного другого повідомлення, жодного keepalive, жодного завершення рукостискання. Щойно інша сторона проговорила, сокет закривається.

SSH: тридцять два байти, і чому далі рушити не можна

Протокол SSH велить обом кінцям спершу надіслати рядок ідентифікації. Наш це один рядок на тридцять два байти, і це єдине, що записує проба. Після цього вона читає: рядок ідентифікації сервера, а потім, якщо сервер надсилає його без запиту, його повідомлення про обмін ключами із запропонованими алгоритмами всередині.

Важливою є та частина, якої немає. Проба ніколи не надсилає власного повідомлення про обмін ключами. Це не політика, це структурна межа: без нього обмін ключами не може відбутися, сесія ніколи не встановлюється, і тому в протоколі немає стану, в якому можна було б надіслати запит на автентифікацію. «Жодної спроби входу» перестає бути правилом, яке комусь треба памʼятати.

Рядок ідентифікації називає продукт, а не вдає із себе клієнта. Адміністратор, який читає свій журнал автентифікації, має мати змогу побачити, хто зʼєднався. Нейтральний або підроблений рядок зробив би законну перевірку схожою на розвідку, а це поганий обмін за ті дві секунди невиразності, які він купує.

Є ціна, і її варто назвати, бо вона проявляється як проблема якості даних, а не як помилка. Деякі сервери чекають на повідомлення клієнта про обмін ключами, перш ніж надіслати власне, і протокол дозволяє будь-який із двох порядків. Коли так стається, проба отримує рядок версії й жодного списку алгоритмів. Покриття для того порту тоді є частковим, і його треба записати як часткове. Якби його записали як повне з порожнім списком, механізм правил дійшов би висновку, що застарілих алгоритмів не пропонують, а це найгірший можливий результат: упевнене «безпечно», виведене з відсутності даних.

Протоколи, які говорять першими, і спокуса відповісти

FTP, Telnet, SMTP і MySQL усі вас вітають. Проба для них не надсилає взагалі нічого: зʼєднатися, прочитати обмежену кількість байтів, закрити. Це не збирання банерів, бо немає способу відкрити такий порт і не отримати привітання, а нічого й не просили.

Спокуса EHLO справжня, і ми вирішили проти неї. Знати, чи поштовий сервер пропонує STARTTLS, справді цінно. Це також вимагає надіслати протокольну команду, а правило було про факти, які інша сторона віддає без запиту. Наслідок у тому, що знахідка SMTP отримує нижчу серйозність і нижчу впевненість, ніж отримала б інакше. Це чесна ціна, і вона має бути видимою в самій знахідці, а не залагодженою словами.

PostgreSQL є цікавим проміжним випадком. Вісім байтів це весь пакет запиту SSL: довжина й магічне число, тобто перше, що надсилає будь-який клієнт, і це не запит, не облікові дані й не вхід. Сервер відповідає одним байтом, який каже, чи говоритиме він TLS. Одного байта відповіді досить, щоб відрізнити базу даних, яка вимагає шифрування, від тієї, що не вимагає, і не існує версії цієї перевірки, яка надсилала б менше.

Відкритий порт не є вразливістю

Те, що порт 445 відповідає, не є увімкненим SMB1. Це два окремі факти, і їм потрібні два окремі поля.

Наша проба записує стан порту, тобто чи є там хтось, і щонайбільше одне свідчення, тобто те, що там сказали. Negotiate, який повертається без старого діалекту, залишає вас із відкритим портом і нічим більше, і це повний результат, а не частковий. Спокуса злити ці дві речі в одну серйозність сильна, бо вона дає значно захопливішу панель.

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

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

Сенсор не має думок

Ще один поділ, і саме він робить решту придатною до супроводу.

Агент вимірює. Він не розвʼязує жодних імен хостів, не тримає власного списку портів і не обирає цілей: адресу й порти виписує платформа, яка перед надсиланням перевірила, що адресу зараз виділено саме тій організації й саме тому серверові. Без цілей агент не вимірює нічого.

Судження відбувається на платформі, за набором правил, який можна прочитати й версіонувати. Це важить, бо знахідка переживає код, який її створив. Через шість місяців хтось спитає, чому сканування у вересні оцінили як високе, і єдиний спосіб на це відповісти це мати правила й виміри окремими речами, які можна дістати поодинці. Якби серйозність запікали в сенсор, відповіддю було б «бо та версія агента так вважала», а це не відповідь.

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

Що ми записали б

  1. Перетворіть обіцянку на число. Якщо ваше правило про те, що ви надсилаєте, рахуйте те, що надсилаєте, і перевіряйте цю кількість. Коментар не може впасти.
  2. Віддавайте перевагу структурним межам. Не надсилати повідомлення про обмін ключами означає, що автентифікація недосяжна, і це сильніше за правило, яке каже не автентифікуватися.
  3. Дозвольте пробі бути частковою. Відсутні дані мають бути видимі як відсутні, бо альтернативою є впевнене заперечення, якого ніхто не може перевірити.
  4. Тримайте «відкрито» і «вразливо» в різних полях, і нехай механізм правил поєднує їх там, де ви можете це прочитати.
  5. Не зберігайте сирих банерів. Нормалізуйте, хешуйте й тримайте хеш для порівняння.
  6. Покладіть думку на платформу, щоб судження минулого кварталу все ще можна було пояснити цього кварталу.

Задля ясності щодо власного продукту: це один детектор, він стоїть за перемикачем на рівні організації, який зараз вимкнено, і його сьогодні не можна купити. Виміри вище взято з реалізації та її тестів, а не з маркетингової сторінки.

Збудовано для Provibr, де агент працює всередині власної мережі клієнта. Перевірковий бік того самого задуму, доведення, що імʼя хоста належить тому, хто питає, описано в як довести право на імʼя хоста.

Читати далі

Почніть з одного хоста й одного агента

Створіть обліковий запис, встановіть агента однією командою та підключіть свій перший хост Proxmox. Пробний період триває 14 днів і не потребує платіжних даних.