Підписування ідентифікаторів ресурсів та захист APIвід DDoS-атак

Хочу розповісти про деякі висновки, які я зробив після роботи над одним із найбільш відвідуваних веб-сайтів у світі.

захист

Мені довелося взяти участь у роботі над цим проектом як консультант. Відвідуваність ресурсу складає близько 200 мільйонів унікальних користувачів на місяць. Така популярність означає і високий рівень ризиків у сфері інформаційної безпеки, зокрема, це ризик зазнати різних видів атак, найпоширеніші серед яких – DDoS. Організація, яку називати не буду, запровадила широкий спектр рішень для запобігання впливу таких атак на працездатність сервісу. Ці захисні системи цілком звичайні. В їх основі – складання контенту на граничних вузлах (CDN, ESI) та застосування багаторівневого пасивного кешу.

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

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

Що таке пасивний кеш?

У подібній конфігурації система підтримки кешу - це сховище даних у форматі "ключ-значення" (наприклад, Redis), а основне джерело даних - це система управління реляційними базами даних (наприклад, Oracle Database).

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

Використання пасивного кешу для захисту від DDoS

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

Розглянута служба блогів використовує активний кеш. Коли клієнт запитує статтю з індексом «1», сервіс звертається до кешу і повертає результат, або (якщо в кеші немає запису з даними запитаної статті), він звертається до бази даних, отримує результат і зберігає його на деякий час у кеші.

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

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

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

Масштабування сховища типу «ключ-значення» виконується швидко і недорого, чого не можна сказати про масштабування системи керуванняреляційними базами даних

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

Розробка сервісів, які використовують пасивний кеш

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

  • По-перше, читання даних можна виконувати лише з кешу.
  • По-друге, після кожної запитаної у сервісу операції створення, оновлення або видалення даних, він повинен поставити відповідне завдання у чергу запитів до сховища вихідних даних.
  • По-третє, після кожного завдання створення, оновлення чи видалення даних, виконаної над даними основного сховища, сервіс повинен ставити у чергу завдання оновлення відповідних ділянок кеша.
Кожну CRUD-операцію системи необхідно реалізувати з урахуванням вищеописаних обмежень.

У процесі розробки час між запитом виконання якоїсь операції та отриманням результату збільшується через наявність черги завдань. Це уповільнює процес розробки та тестування. Крім того, програмісту потрібно знати про специфічні помилки, які можуть виникнути через застарілі дані в кеші.

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

Розробляти системи, що використовують пасивний кеш, складніше, але коли на першому місці – безпека, зазвичай на подібні складності уваги не звертають. Однак це не означає, що всі способи захисту від DDoS-атакОбов'язково вимагатимуть великих зусиль. Вище ми розглядали приклад атаки на службу блогів шляхом перебору ідентифікаторів статей. Пом'якшити наслідки таких атак можна, зробивши ідентифікатори ресурсів непередбачуваними.

Підписування ідентифікаторів ресурсів

Причина, через яку системи з активним кешем схильні до вищеописаних атак, полягає в тому, що зловмисник може легко сконструювати ідентифікатор ресурсу. Незалежно від того, чи є ідентифікатор числовим ID (таким, як у нашому прикладі), закодованим у base64 GUID, як в API GraphQL, або UUID, як у більшості документ-орієнтованих баз даних, проблема полягає в тому, що коли сервер отримує запит, невідомо, чи існує запитаний ресурс. Єдиний спосіб це з'ясувати - виконати звернення, або до кешу, або до основного джерела даних, і дочекатися відповіді. Для того, щоб сервер, ні до чого не звертаючись, міг би визначити, чи існує запитаний ресурс, ідентифікатори ресурсів можна підписати.

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

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

Я використовую такий підхід під час створення ідентифікаторів ресурсів GraphQL. Зокрема, проксі, який перенаправляє запити GraphQL, попередньо перевіряє, чи дійсний ID ресурсу.

Signed GUID, абоsguid – це пакт для Node.js, у який я виніс процедури створення та перевірки підписаних ідентифікаторів. Підписати ідентифікатор можна за допомогою команди toSguid. Для перевірки та відкриття підписаних ідентифікаторів використовується команда fromSguid. Виглядає це так:

На додаток до підписування ідентифікаторів, Sguid розрахований на використання просторів імен та ідентифікаторів типу ресурсу. Це забезпечує глобальну унікальність ідентифікаторів.

Sguid використовує криптосистему з відкритим ключем Ed25519. Підпис, що вийшов, кодується з використанням URL-кодування base64.

Мінус такого підходу – ідентифікатори, які незручно використовувати людям:

Плюс – масштабований захист від DDoS-атак, що проводяться на прикладному рівні моделі OSI, без надмірного ускладнення процесу розробки.

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

Крім того, треба пам'ятати про те, що у справі захисту від кібератак важливим є те, як результати нападу виглядають з точки зору зловмисника. Для того, щоб, перебираючи ідентифікатори ресурсів, «пробити» ефективно сконструйовану систему, яка використовує описаний підхід, знадобиться серйозна потужність. Можливо, атакуючий просто не розраховує на подібне, і бачачи, що система на його дії не реагує (хоча вона цілком може працювати на межі можливостей), вирішить, що він уже випробував усе, що можна, і припинить атаку.