Web-ul crește într-un ritm masiv. Tot mai multe aplicații web sunt dinamice, captivante și nu necesită reîmprospătarea utilizatorului final. Există suport nou pentru tehnologiile de comunicare cu latență scăzută, cum ar fi websockets. Websocket-urile ne permit să realizăm o comunicare în timp real între diferiți clienți conectați la un server.

O mulțime de oameni nu știu cum să-și protejeze websocket-urile împotriva unor atacuri foarte frecvente. Lăsați-ne să vedem ce sunt și ce ar trebui să faceți pentru a vă proteja websocket-urile.

# 0: Activați CORS

WebSocket nu vine cu CORS încorporat. Acestea fiind spuse, înseamnă că orice site web se poate conecta la orice altă conexiune de web site și poate comunica fără nicio restricție! Nu analizez motivele pentru care așa este, dar o soluție rapidă la acest lucru este să verific Origin antet pe strângerea de mână a mufei web.

Sigur, antetul Origin poate fi falsificat de către un atacator, dar nu contează, deoarece pentru a-l exploata, atacatorul trebuie să falsifice antetul Origin din browserul victimei, iar browserele moderne nu permit javascript-ului normal așezat în browserele web să schimbe antetul Origin .

Mai mult, dacă autentificați utilizatorii folosind, de preferință, cookie-uri, atunci aceasta nu este cu adevărat o problemă pentru dvs. (mai multe despre acest lucru la punctul # 4)

# 1: Implementați limitarea ratei

Limitarea ratei este importantă. Fără aceasta, clienții pot efectua cu bună știință sau fără știință un atac DoS pe serverul dvs. DoS înseamnă Denial of Service. DoS înseamnă că un singur client ține serverul atât de ocupat încât serverul nu poate gestiona alți clienți.

În majoritatea cazurilor, este o încercare deliberată a unui atacator de a doborî un server. Uneori, implementările slabe ale frontend-ului pot duce și la DoS de către clienții normali.

Vom face uz de algoritmul bucket leaky (care se pare că este un algoritm foarte comun pentru rețele de implementat) pentru a implementa limitarea ratei pe web-urile noastre.

Ideea este că aveți o găleată care are o gaură de dimensiuni fixe la podea. Începi să pui apă în ea și apa iese prin gaura din partea de jos. Acum, dacă rata de introducere a apei în găleată este mai mare decât rata de scurgere din gaură pentru o lungă perioadă de timp, la un moment dat, găleată se va umple și va începe să se scurgă. Asta e tot.

Să înțelegem acum legătura cu portul nostru web:

  1. Apa este traficul de websocket trimis de utilizator.
  2. Apa trece pe gaură. Acest lucru înseamnă că serverul a procesat cu succes acea solicitare de portal web.
  3. Apa care este încă în găleată și care nu s-a revărsat este practic în așteptarea traficului. Serverul va procesa acest trafic mai târziu. Acest lucru ar putea fi, de asemenea, un flux de trafic spargător (adică prea mult trafic pentru o perioadă foarte mică de timp este în regulă atâta timp cât cupa nu se scurge)
  4. Apa care se revarsă este traficul eliminat de server (prea mult trafic provenind de la un singur utilizator)

Ideea este că trebuie să vă verificați activitatea web și să determinați aceste numere. Veți atribui o găleată fiecărui utilizator. Decidem cât de mare ar trebui să fie bucket-ul (trafic pe care un singur utilizator ar putea să-l trimită într-o perioadă fixă) în funcție de cât de mare este gaura dvs. (cât timp are nevoie în medie serverul dvs. pentru a procesa o singură solicitare de websocket, spunem salvarea unui mesaj trimis de către un utilizator într-o bază de date).

Aceasta este o implementare redusă pe care o folosesc codedamn pentru implementarea algoritmului de bucket scurgeri pentru websockets. Este în NodeJS, dar conceptul rămâne același.

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

Deci, ce se întâmplă aici? Practic, dacă limita este depășită, precum și limita de explozie (care sunt constante stabilite), conexiunea websocket scade. În caz contrar, după o anumită întârziere, vom reseta contorul de explozie. Acest lucru lasă din nou spațiu pentru o altă explozie.

# 2: Limitați dimensiunea sarcinii utile

Aceasta ar trebui să fie implementată ca o caracteristică în cadrul bibliotecii dvs. de porturi web de pe server. Dacă nu, este timpul să îl schimbați într-unul mai bun! Ar trebui să limitați lungimea maximă a mesajului care ar putea fi trimis prin portul web. Teoretic nu există nicio limită. Desigur, obținerea unei sarcini utile uriașe este foarte probabil să blocheze acea instanță de soclu specială și să mănânce mai multe resurse de sistem decât este necesar.

De exemplu, dacă utilizați biblioteca WS pentru Node pentru crearea de websockets pe server, puteți utiliza opțiunea maxPayload pentru a specifica dimensiunea maximă a sarcinii utile în octeți. Dacă dimensiunea sarcinii utile este mai mare decât aceasta, biblioteca va renunța în mod nativ la conexiune.

Nu încercați să implementați acest lucru singur, determinând lungimea mesajului. Nu vrem să citim mai întâi întregul mesaj în memoria RAM a sistemului. Dacă este chiar cu 1 octet mai mare decât limita stabilită, renunțați la acesta. Acest lucru ar putea fi implementat doar de bibliotecă (care gestionează mesajele ca un flux de octeți, mai degrabă decât șiruri fixe).

# 3: Creați un protocol solid de comunicare

Deoarece acum aveți o conexiune duplex, ați putea trimite orice la server. Serverul ar putea trimite orice text înapoi clientului. Trebuie să aveți o modalitate de comunicare eficientă între amândoi.

Nu puteți trimite mesaje brute dacă doriți să scalați aspectul de mesagerie al site-ului dvs. web. Prefer să folosesc JSON, dar există și alte modalități optimizate de a configura o comunicare. Cu toate acestea, având în vedere JSON, iată cum ar arăta o schemă de mesagerie de bază pentru un site generic:

Client to Server (or vice versa): { status: "ok"|"error", event: EVENT_NAME, data: <any arbitrary data> }

Acum este mai ușor pentru dvs. de pe server să verificați dacă există evenimente și format valide. Renunțați imediat la conexiune și înregistrați adresa IP a utilizatorului dacă formatul mesajului diferă. Nu există nicio modalitate în care formatul s-ar schimba, cu excepția cazului în care cineva va furniza manual conexiunea dvs. web. Dacă sunteți pe nod, vă recomand să utilizați fișierul Biblioteca Joi pentru validarea ulterioară a datelor primite de la utilizator.

# 4: Autentificați utilizatorii înainte de stabilirea conexiunii WS

Dacă utilizați websocket-uri pentru utilizatorii autentificați, este o idee destul de bună să permiteți utilizatorilor autentificați să stabilească o conexiune websocket de succes. Nu permiteți nimănui să stabilească o conexiune și apoi așteptați ca aceștia să se autentifice prin intermediul portalului web în sine. Mai întâi de toate, stabilirea unei conexiuni de tip websocket este oricum cam scumpă. Deci, nu doriți ca persoanele neautorizate să sară pe site-urile dvs. web și să conecteze conexiuni care ar putea fi utilizate de alte persoane.

Pentru a face acest lucru, atunci când stabiliți o conexiune pe frontend, treceți câteva date de autentificare către websocket. Ar putea fi un antet precum X-Auth-Token: . În mod implicit, cookie-urile ar fi transmise oricum.

Din nou, chiar se rezumă la biblioteca pe care o utilizați pe server pentru implementarea websocket-urilor. Dar dacă sunteți pe Node și utilizați WS, există asta verifyClient funcție care vă permite accesul la obiectul info transmis unei conexiuni websocket. (La fel cum ai avea acces la obiectul de cerere pentru solicitări HTTP.)

# 5: Utilizați SSL peste websockets

Aceasta este o nebunie, dar tot trebuie spus. Folosiți wss: // în loc de ws: //. Acest lucru adaugă un strat de securitate peste comunicarea dvs. Utilizați un server precum Nginx pentru serverele de proxy inversate și activați SSL peste ele. Configurarea Nginx ar fi un alt tutorial. Voi lăsa directiva pe care trebuie să o folosiți pentru Nginx pentru cei care sunt familiarizați cu ea. Mai multe informații aici.

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

Aici se presupune că serverul tău de websocket ascultă pe portul 1337 și utilizatorii tăi se conectează la websocket-ul tău în acest mod:

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

Întrebări?

Aveți întrebări sau sugestii? Întreabă!