Как да защитим вашите WebSocket връзки

Мрежата расте с огромни темпове. Все повече и повече уеб приложения са динамични, завладяващи и не изискват от крайния потребител да се опреснява. Появява се поддръжка за комуникационни технологии с ниска латентност, като уеб сокети. Websockets ни позволяват да постигнем комуникация в реално време между различни клиенти, свързани към сървър.

Много хора не знаят как да защитят уебсайтовете си срещу някои много често срещани атаки. Нека да видим какви са те и какво трябва да направите, за да защитите уебсайтовете си.

# 0: Активиране на CORS

WebSocket не се предлага с вграден CORS. Като се има предвид това, това означава, че всеки уебсайт може да се свърже с интернет връзката на всеки друг уебсайт и да комуникира без никакви ограничения! Няма да навлизам в причините, поради които е така, но бързото решение на това е да проверите Originзаглавката на ръкостискането на уеб сайта.

Разбира се, заглавието на Origin може да бъде фалшифицирано от нападател, но няма значение, защото за да го използва, нападателят трябва да фалшифицира заглавието Origin в браузъра на жертвата, а съвременните браузъри не позволяват на нормалния javascript, който седи в уеб браузърите, да промени заглавката на Origin .

Освен това, ако всъщност удостоверявате потребителите, използвайки, за предпочитане, бисквитки, това всъщност не е проблем за вас (повече за това в точка # 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, за да посочите максималния размер на полезния товар в байтове. Ако размерът на полезния товар е по-голям от това, библиотеката първоначално ще прекъсне връзката.

Не се опитвайте да прилагате това сами, като определяте дължината на съобщението. Не искаме първо да прочетем цялото съобщение в системната RAM. Ако е дори с 1 байт по-голям от зададения ни лимит, пуснете го. Това може да бъде приложено само от библиотеката (която обработва съобщенията като поток от байтове, а не като фиксирани низове).

# 3: Създайте солиден протокол за комуникация

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

Не можете да изпращате необработени съобщения, ако искате да мащабирате аспекта на съобщенията на вашия уебсайт. Предпочитам да използвам JSON, но има и други оптимизирани начини за настройване на комуникация. Въпреки това, като се има предвид JSON, ето как би изглеждала основна схема за съобщения за общ сайт:

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

Сега е по-лесно за вас на сървъра да проверявате за валидни събития и формат. Прекъснете връзката незабавно и регистрирайте IP адреса на потребителя, ако форматът на съобщението се различава. Няма начин форматът да се промени, освен ако някой ръчно не изтръпва с вашата връзка с уебсайта. Ако сте на възел, препоръчвам да използвате библиотеката Joi за допълнителна проверка на входящите данни от потребителя.

# 4: Удостоверява потребителите, преди да се установи WS връзка

Ако използвате websockets за удостоверени потребители, е доста добра идея да разрешите само на удостоверени потребители да установят успешна връзка с websocket. Не позволявайте на никого да установява връзка и след това изчакайте да се удостовери чрез самия уебсайт. На първо място, установяването на websocket връзка така или иначе е малко скъпо. Така че не искате неупълномощени хора да скачат на уебсайтовете ви и да свързват връзки, които могат да бъдат използвани от други хора.

За да направите това, когато установявате връзка на интерфейс, предайте някои данни за удостоверяване на websocket. Може да е заглавка като X-Auth-Token:. По подразбиране бисквитките така или иначе ще бъдат предадени.

Отново това наистина се свежда до библиотеката, която използвате на сървъра за внедряване на уеб сайтове. Но ако сте на 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"; }

Тук се приема, че вашият сървър на websocket слуша на порт 1337 и вашите потребители се свързват с него по този начин:

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

Въпроси?

Have any questions or suggestions? Ask away!