Comment sécuriser vos connexions WebSocket

Le Web se développe à un rythme effréné. De plus en plus d'applications Web sont dynamiques, immersives et ne nécessitent pas d'actualisation de l'utilisateur final. Il existe un nouveau support pour les technologies de communication à faible latence comme les Websockets. Les Websockets nous permettent d'établir une communication en temps réel entre différents clients connectés à un serveur.

Beaucoup de gens ne savent pas comment sécuriser leurs Websockets contre certaines attaques très courantes. Voyons ce qu'ils sont et que devez-vous faire pour protéger vos Websockets.

# 0: Activer CORS

WebSocket n'est pas fourni avec CORS intégré. Cela étant dit, cela signifie que n'importe quel site Web peut se connecter à la connexion Websocket de n'importe quel autre site Web et communiquer sans aucune restriction! Je ne vais pas expliquer pourquoi c'est ainsi, mais une solution rapide à cela consiste à vérifier l'en- Origintête sur la poignée de main Websocket.

Bien sûr, l'en-tête Origin peut être falsifié par un attaquant, mais cela n'a pas d'importance, car pour l'exploiter, l'attaquant doit simuler l'en-tête Origin sur le navigateur de la victime, et les navigateurs modernes ne permettent pas au javascript normal assis dans les navigateurs Web de changer l'en-tête Origin .

De plus, si vous authentifiez réellement les utilisateurs en utilisant, de préférence, des cookies, ce n'est pas vraiment un problème pour vous (plus à ce sujet au point 4)

# 1: Mettre en œuvre la limitation de débit

La limitation de débit est importante. Sans cela, les clients peuvent sciemment ou non une attaque DoS sur votre serveur. DoS signifie déni de service. DoS signifie qu'un seul client maintient le serveur tellement occupé qu'il est incapable de gérer d'autres clients.

Dans la plupart des cas, il s'agit d'une tentative délibérée de la part d'un attaquant de faire tomber un serveur. Parfois, de mauvaises implémentations frontales peuvent également conduire à un DoS par des clients normaux.

Nous allons utiliser l'algorithme de seau qui fuit (qui est apparemment un algorithme très courant pour les réseaux à implémenter) pour implémenter la limitation de débit sur nos websockets.

L'idée est que vous avez un seau qui a un trou de taille fixe à son plancher. Vous commencez à y mettre de l'eau et l'eau sort par le trou au fond. Maintenant, si votre taux de mise en eau dans le seau est plus grand que le taux de sortie du trou pendant une longue période, à un moment donné, le seau deviendra plein et commencera à fuir. C'est tout.

Comprenons maintenant comment cela se rapporte à notre websocket:

  1. L'eau est le trafic Websocket envoyé par l'utilisateur.
  2. L'eau passe dans le trou. Cela signifie que le serveur a traité avec succès cette requête Websocket particulière.
  3. L'eau qui est toujours dans le seau et qui n'a pas débordé est essentiellement un trafic en attente. Le serveur traitera ce trafic ultérieurement. Cela peut également être un flux de trafic en rafale (c'est-à-dire que trop de trafic pendant une très courte période est acceptable tant que le compartiment ne fuit pas)
  4. L'eau qui déborde est le trafic rejeté par le serveur (trop de trafic provenant d'un seul utilisateur)

Le point ici est que vous devez vérifier votre activité Websocket et déterminer ces chiffres. Vous allez attribuer un bucket à chaque utilisateur. Nous décidons de la taille du bucket (trafic qu'un seul utilisateur pourrait envoyer sur une période fixe) en fonction de la taille de votre trou (de combien de temps en moyenne votre serveur a-t-il besoin pour traiter une seule requête Websocket, par exemple enregistrer un message envoyé par un utilisateur dans une base de données).

Il s'agit d'une implémentation réduite que j'utilise chez codedamn pour implémenter un algorithme de seau qui fuit pour les websockets. C'est dans NodeJS mais le concept reste le même.

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

Alors que se passe-t-il ici? Fondamentalement, si la limite est dépassée ainsi que la limite de rafale (qui sont des constantes définies), la connexion Websocket tombe. Sinon, après un certain délai, nous allons réinitialiser le compteur de rafales. Cela laisse de la place pour une autre rafale.

# 2: Restreindre la taille de la charge utile

Cela doit être implémenté en tant que fonctionnalité dans votre bibliothèque websocket côté serveur. Sinon, il est temps de le changer pour un meilleur! Vous devez limiter la longueur maximale du message pouvant être envoyé sur votre websocket. Théoriquement, il n'y a pas de limite. Bien sûr, obtenir une charge utile énorme est très susceptible de bloquer cette instance de socket particulière et de consommer plus de ressources système que nécessaire.

Par exemple, si vous utilisez la bibliothèque WS pour Node pour créer des websockets sur le serveur, vous pouvez utiliser l'option maxPayload pour spécifier la taille maximale de la charge utile en octets. Si la taille de la charge utile est plus grande que cela, la bibliothèque abandonnera nativement la connexion.

N'essayez pas de l'implémenter vous-même en déterminant la longueur du message. Nous ne voulons pas lire tout le message dans la RAM du système en premier. S'il est même 1 octet supérieur à notre limite définie, supprimez-le. Cela ne pourrait être implémenté que par la bibliothèque (qui gère les messages comme un flux d'octets plutôt que des chaînes fixes).

# 3: Créez un protocole de communication solide

Parce que maintenant vous êtes sur une connexion duplex, vous pourriez envoyer n'importe quoi au serveur. Le serveur peut renvoyer n'importe quel texte au client. Vous auriez besoin d'un moyen de communication efficace entre les deux.

Vous ne pouvez pas envoyer de messages bruts si vous souhaitez mettre à l'échelle l'aspect de la messagerie de votre site Web. Je préfère utiliser JSON, mais il existe d'autres moyens optimisés pour configurer une communication. Cependant, compte tenu de JSON, voici à quoi ressemblerait un schéma de messagerie de base pour un site générique:

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

Il est maintenant plus facile pour vous sur le serveur de vérifier les événements et le format valides. Supprimez immédiatement la connexion et enregistrez l'adresse IP de l'utilisateur si le format du message diffère. Il n'y a aucun moyen que le format change à moins que quelqu'un ne pique manuellement avec votre connexion Websocket. Si vous êtes sur le nœud, je vous recommande d'utiliser la bibliothèque Joi pour une validation plus approfondie des données entrantes de l'utilisateur.

# 4: Authentifiez les utilisateurs avant l'établissement de la connexion WS

Si vous utilisez des websockets pour des utilisateurs authentifiés, il est très judicieux d'autoriser uniquement les utilisateurs authentifiés à établir une connexion websocket réussie. N'autorisez personne à établir une connexion, puis attendez qu'il s'authentifie via le websocket lui-même. Tout d'abord, établir une connexion Websocket coûte de toute façon un peu cher. Vous ne voulez donc pas que des personnes non autorisées sautent sur vos sockets Web et accaparent des connexions qui pourraient être utilisées par d'autres personnes.

Pour ce faire, lorsque vous établissez une connexion sur le frontend, transmettez certaines données d'authentification à websocket. Cela pourrait être un en-tête comme X-Auth-Token:. Par défaut, les cookies seraient quand même transmis.

Encore une fois, cela dépend vraiment de la bibliothèque que vous utilisez sur le serveur pour implémenter des websockets. Mais si vous êtes sur Node et que vous utilisez WS, il existe cette fonction verifyClient qui vous permet d'accéder à l'objet info transmis à une connexion websocket. (Tout comme vous avez accès à l'objet req pour les requêtes HTTP.)

# 5: Utilisez SSL sur les Websockets

C'est une évidence, mais il faut encore le dire. Utilisez wss: // au lieu de ws: //. Cela ajoute une couche de sécurité sur votre communication. Utilisez un serveur comme Nginx pour le proxy inverse des Websockets et activez SSL sur eux. La configuration de Nginx serait un tout autre tutoriel. Je laisserai la directive que vous devez utiliser pour Nginx aux personnes qui la connaissent. Plus d'infos ici.

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"; }

Ici, il est supposé que votre serveur websocket écoute sur le port 1337 et que vos utilisateurs se connectent à votre websocket de cette manière:

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

Des questions?

Have any questions or suggestions? Ask away!