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

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



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

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

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

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

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

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

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

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

Наприклад, http://gajus.com/blog/ — це служба блогів. Тут розміщують статті. Клієнт може отримувати доступ до окремих статей, використовуючи свої унікальні індекси. Ось приклади адрес статей:

У цьому прикладі«1», «2», «8» і «9» — це ідентифікатори ресурсів, унікальні індекси, які застосовуються для доступу до даних у сховище.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

import {
fromSguid,
InvalidSguidError,
toSguid,
} from 'sguid';
const secretKey = '6h2K+JuGfWTrs5Lxt+mJw9y5q+mXKCjiJgngIDWDFy23TWmjpfCnUBdO1fdzi6mxhmo2ntpazsntcc2wuqrxvq==';
const publicKey = 't01po6Xwp1AXTtXw84ujMRzDtp0z2s7J03atslkk8vu=';
const namespace = 'gajus';
const resourceTypeName = 'article';
const generateArticleSguid = (articleId: number): string => {
return toSguid(secretKey, namespace, resourceTypeName, articleId);
};
const parseArticleSguid = (articleGuide: string): id => {
try {
return fromSguid(publicKey, namespace, resourceTypeName, articleSguid).id;
} catch (error) {
if (error instanceof InvalidSguidError) {
// Handle error.
}
throw error;
}
};

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

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

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

pbp3h9nTr0wPboKaWrg_Q77KnZW1-rBkwzzYJ0Px9Qvbq0KQvcfuR2uCRCtijqysx98g1f50k50x5ykicgnpansiawqiojesim5hbwvzcgfjzsi6imdhanvziiwidhlwzsi6imfydgljbguifq

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

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

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

А як ви захищаєтеся від DDoS-атак?
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.