Як захистити свої з’єднання WebSocket

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

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

# 0: Увімкнути CORS

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

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

Більше того, якщо ви насправді аутентифікуєте користувачів, використовуючи, бажано, файли cookie, то це насправді для вас не проблема (детальніше про це в пункті 4)

№1: Обмеження швидкості впровадження

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

У більшості випадків це навмисна спроба зловмисника зламати сервер. Іноді погані реалізації інтерфейсу також можуть призвести до DoS звичайних клієнтів.

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

Ідея полягає в тому, що у вас є відро, у якого в підлозі є отвір фіксованого розміру. Ви починаєте класти туди воду, і вона виходить через отвір на дні. Тепер, якщо ваш показник подачі води у відро надовго перевищує швидкість витікання з отвору, у певний момент відро заповниться і почне витікати. Це все.

Давайте тепер зрозуміємо, як це пов’язано з нашим веб-сокетом:

  1. Вода - це інтернет-трафік, що надсилається користувачем.
  2. Вода проходить по отвору. Це означає, що сервер успішно обробив конкретний запит веб-сокета.
  3. Вода, яка все ще знаходиться у відрі та не перелилася, в основному очікує руху. Сервер буде обробляти цей трафік пізніше. Це також може бути бурхливим потоком руху (тобто занадто багато трафіку за дуже малий час - це нормально, поки ківш не витікає)
  4. Переповнена вода - це трафік, який сервер відкидає (занадто багато трафіку надходить від одного користувача)

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

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

if(this.limitCounter >= Socket.limit) { if(this.burstCounter >= Socket.burst) { return 'Bucket is leaking' } ++this.burstCounter return setTimeout(() => { this.verify(callingMethod, ...args) setTimeout(_ => --this.burstCounter, Socket.burstTime) }, Socket.burstDelay) } ++this.limitCounter

То що тут відбувається? В основному, якщо обмеження перетнуто так само, як і обмеження сплеску (які є константами), з'єднання з веб-розеткою падає. В іншому випадку після певної затримки ми скинемо лічильник серій. Це знову залишає простір для чергового сплеску.

№2: Обмежте розмір корисного навантаження

Це має бути реалізовано як функція у вашій бібліотеці веб-розетки на стороні сервера. Якщо ні, то час змінити його на кращий! Ви повинні обмежити максимальну довжину повідомлення, яке може бути надіслане через ваш веб-сокет. Теоретично обмежень немає. Звичайно, отримання величезного корисного навантаження дуже ймовірно призведе до того, що саме цей екземпляр сокета з’їсть більше системних ресурсів, ніж потрібно.

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

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

# 3: Створіть надійний протокол зв'язку

Оскільки зараз ви маєте дуплексне з’єднання, ви можете надсилати що-небудь на сервер. Сервер може відправити будь-який текст назад клієнту. Ви повинні мати спосіб ефективного спілкування між ними.

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

Client to Server (or vice versa):  status: "ok"

Тепер вам на сервері легше перевіряти дійсні події та формат. Негайно припиніть підключення та зареєструйте IP-адресу користувача, якщо формат повідомлення відрізняється. Формат не зміниться, якщо хтось вручну не поколює ваше підключення до Інтернету. Якщо ви використовуєте node, я рекомендую використовувати бібліотеку Joi для подальшої перевірки вхідних даних від користувача.

№4: Аутентифікація користувачів до встановлення з'єднання WS

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

Для цього, коли ви встановлюєте підключення на інтерфейсі, передайте деякі дані автентифікації до веб-сокета. Це може бути заголовок типу X-Auth-Token:. За замовчуванням файли cookie все одно передаються.

Знову ж таки, це справді зводиться до бібліотеки, яку ви використовуєте на сервері для реалізації веб-сокетів. Але якщо ви використовуєте Node і використовуєте WS, існує функція verifyClient, яка надає вам доступ до інформаційного об'єкта, переданого до з'єднання websocket. (Так само, як у вас є доступ до об'єкта req для запитів HTTP.)

# 5: Використовуйте SSL через веб-сокети

Це ні до чого, але все одно потрібно сказати. Використовуйте wss: // замість ws: //. Це додає рівень безпеки над вашим спілкуванням. Використовуйте сервер, такий як Nginx, для зворотного проксірування веб-сокетів і ввімкніть SSL над ними. Налаштування Nginx було б зовсім іншим підручником. Я залишу директиву, яку вам потрібно використовувати для Nginx, для тих, хто з нею знайомий. Більше інформації тут.

location /your-websocket-location/ { proxy_pass ​//127.0.0.1:1337; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; }

Тут передбачається, що ваш веб-сервер прослуховує порт 1337, а ваші користувачі підключаються до вашого веб-сокета таким чином:

const ws = new WebSocket('wss://yoursite.com/your-websocket-location')

Питання?

Have any questions or suggestions? Ask away!