WebSockets : fonctionnement, vulnérabilités et bonnes pratiques sécurité

WebSocket est un protocole de communication bidirectionnel en temps réel, conçu pour un échange efficace de données entre un client (navigateur web, application) et un serveur.

Contrairement aux requêtes HTTP classiques, qui nécessitent d’ouvrir une nouvelle connexion à chaque échange, WebSocket établit une connexion persistante. Une fois activée, elle permet aux deux parties de transmettre des messages en continu, sans interruption.

Ce mode de fonctionnement, dit full-duplex, autorise le serveur à envoyer des données spontanément, sans requête préalable du client.

Ce protocole est particulièrement adapté aux applications interactives et aux fonctionnalités nécessitant une mise à jour en temps réel. Il est couramment utilisé pour les systèmes de messagerie, de notifications ou de collaboration instantanée.

Des frameworks comme Meteor simplifient son intégration et permettent de l’exploiter à grande échelle dans une application.

Guide complet sur les WebSockets

Fonctionnement des WebSockets

Avant d’établir une connexion WebSocket, un handshake doit être effectué via le protocole HTTP :

GET /chat HTTP/1.1
Host: www.websocket.com:8000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: */*
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Sec-WebSocket-Version: 13
Origin: http://www.websocket.com
Sec-WebSocket-Key: F3K8tSSU8iTVlhenxKqtbw==
DNT: 1
Connection: keep-alive, Upgrade
Cookie: X-Authorization=8jvbphlmktyutyty4vraWBA
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket

Si le serveur accepte la connexion, il répond de la façon suivante :

HTTP/1.1 101 Switching Protocols
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Accept: ILZpbBQBard/ixWRPPI6UOlh8hY=

Une réponse avec un code de statut 101 indique que le serveur a validé la connexion, permettant ainsi l’instanciation de la WebSocket. Les données échangées peuvent être de différents formats (HTML, JSON, texte, etc.).

Comme HTTP, WebSocket ne dispose d’aucun mécanisme de sécurité intégré. Il revient donc au développeur d’implémenter les protections nécessaires au niveau applicatif.

Dans l’exemple précédent, le cookie X-Authorization sert de mécanisme d’authentification pour identifier l’utilisateur demandant l’accès à la WebSocket, permettant ainsi de restreindre les données accessibles selon ses permissions.

Fonctionnement des WebSockets
Communication WebSocket

Autre point commun avec HTTP : l’existence d’une version sécurisée. Le protocole définit WS (WebSocket) et WSS (WebSocket Secure). Ce dernier chiffre les communications, empêchant ainsi un attaquant d’intercepter les données en clair via une attaque de type Man in the Middle, comme cela peut se produire avec HTTP non sécurisé.

Par ailleurs, des mécanismes de gestion des connexions inutilisées peuvent être mis en place pour optimiser les ressources serveur et prévenir les attaques par déni de service, où un utilisateur malveillant ouvrirait un grand nombre de connexions simultanées.

Comment tester la sécurité des WebSockets ?

Avant de détailler les vulnérabilités des WebSockets, il est essentiel de comprendre comment inspecter les requêtes échangées entre le client et le serveur, ainsi que la manière de modifier les données transmises.

Burp Suite est l’outil de référence pour les tests d’intrusion web. Il permet notamment d’intercepter, d’analyser et de rejouer les requêtes. Bien qu’initialement conçu pour le protocole HTTP, il offre également des fonctionnalités spécifiques à l’analyse des WebSockets.

Historique des WebSockets
Historique des WebSockets

Dans un premier temps, il est essentiel d’observer l’ensemble des requêtes échangées afin d’identifier les points d’entrée potentiels pour un attaquant ; et de comprendre comment les entrées utilisateur sont traitées par le navigateur et le serveur.

Une fois l’application bien analysée, il devient pertinent de manipuler ces entrées et de rejouer les requêtes les plus intéressantes.

À l’instar des requêtes HTTP, les WebSockets peuvent être envoyées dans le Repeater pour être modifiées et rejouées. Toutefois, leur interface présente quelques différences.

Interface du Repeater
Interface du Repeater

Dans le panneau de gauche, il est possible de modifier les données et de choisir d’envoyer la requête soit au serveur, soit au client.

Le panneau de droite affiche la requête envoyée ainsi que la réponse retournée par le serveur (indiquée ici en rouge).

Cependant, Burp Suite présente certaines limitations pour l’analyse des WebSockets. Il n’est notamment pas possible d’utiliser l’onglet Intruder, qui permet d’automatiser l’envoi de requêtes en s’appuyant sur un dictionnaire de données. Cette fonctionnalité serait utile, par exemple, pour un brute force visant à tester des mots de passe faibles.

De plus, le scanner automatisé de la version Professional de Burp Suite ne prend pas en charge les WebSockets.

Malgré ces contraintes, Burp Suite reste un outil largement suffisant si seules certaines fonctionnalités de l’application exploitent ce protocole. Pour des tests de sécurité plus avancés, il est possible d’utiliser un autre proxy dédié, comme ZAP.

Wscat est un outil léger et simple d’utilisation, installé via npm. L’utilisateur renseigne l’URL du serveur, ce qui déclenche le handshake initial via HTTP, avant d’établir une connexion WebSocket.

Une fois connecté, il peut envoyer des données directement depuis la console et recevoir celles transmises par le serveur. Par exemple, un endpoint pourrait permettre d’exécuter la commande ping sur une adresse IP fournie par l’utilisateur.

Bien que facile à prendre en main, Wscat reste limité pour des tests avancés, offrant peu de fonctionnalités d’analyse ou d’automatisation.

Utilisation de wscat
Utilisation de wscat

STEWS est un outil conçu spécifiquement pour tester la sécurité des WebSockets. Il propose trois modules : Discovery, Fingerprinting et Vulnerability Detection.

Le module Discovery permet de localiser les endpoints du serveur utilisant le protocole WebSocket. Il fonctionne de manière similaire à des outils comme feroxbuster ou ffuf, en essayant des noms d’endpoints courants (issus d’un dictionnaire) et en analysant les réponses du serveur.

Le module Fingerprinting permet d’identifier le type de serveur WebSocket utilisé par l’application. Cette information peut être précieuse pour cibler des attaques spécifiques en fonction du serveur identifié, et vérifier si des CVE sont disponibles pour celui-ci.

Module Fingerprinting
Module Fingerprinting

Dans cet exemple, l’outil a identifié l’utilisation de la librairie Ratchet pour les communications WebSocket.

Le module Vulnerability Detection permet de tester plusieurs failles connues, telles que le CSWH (Cross-Site WebSocket Hijacking), que nous aborderons plus tard, ainsi que diverses CVE en fonction des librairies détectées.

Module Vulnerability detection
Module Vulnerability detection

Quelles sont les vulnérabilités courantes des WebSockets et comment se protéger ?

Ce type de vulnérabilité correspond à l’exploitation d’une faille CSRF (Cross-Site Request Forgery) dans le cadre d’une communication WebSocket.

Le scénario d’exploitation est similaire à celui du CSRF classique. Un code malveillant est intégré dans une page que l’attaquant cherche à faire visiter à la victime.

Prenons l’exemple d’un changement de mot de passe vulnérable : l’attaquant incite la victime à exécuter la requête de changement de mot de passe. Ainsi, il peut obtenir le nouveau mot de passe et accéder au compte de la victime.

Script d’exploitation du CSWH
Script d’exploitation du CSWH

En analysant les requêtes envoyées dans le contexte de la victime, on constate que le handshake a réussi, car le cookie de la victime est automatiquement transmis par le navigateur, et aucune protection CSRF n’est en place.

La WebSocket chargée d’effectuer le changement de mot de passe sera ensuite instanciée, et les données seront envoyées au serveur, qui appliquera le changement.

Il est important de noter que ce type d’exploitation n’est possible que si le cookie a le flag Same-Site défini sur None (les navigateurs modernes attribuent généralement la valeur Lax par défaut si ce flag n’est pas spécifié).

Pour corriger ce problème dans une application, il est essentiel de commencer par modifier le flag Same-Site du cookie de session. Ensuite, le serveur doit vérifier l’en-tête Origin et le comparer à une liste blanche de domaines autorisés.

Les problèmes de gestion des droits sont fréquents dans les applications web et peuvent également survenir dans le cadre des connexions WebSocket.

Prenons un exemple où le serveur envoie des notifications à l’utilisateur lorsqu’il est mentionné sur une page. Les WebSockets sont alors particulièrement adaptées à ce cas.

Cependant, une erreur courante serait d’envoyer la notification à tous les clients connectés au serveur WebSocket, et de laisser le navigateur afficher la notification uniquement si l’utilisateur est concerné.

Lors d’un audit de sécurité, cette erreur serait facilement détectée, car l’auditeur verrait, dans l’historique du proxy, les notifications destinées à tous les autres utilisateurs.

Bien que cette approche semble évidente comme une vulnérabilité, un développeur pourrait ne pas saisir le risque de diffuser ainsi l’information s’il ne surveille pas les requêtes envoyées par son application via un proxy.

Nous allons conclure par une vulnérabilité qui n’est pas spécifique à HTTP, mais qui souligne l’importance de contrôler les données utilisateur. Le fait que ces données soient envoyées via WebSocket ne doit en aucun cas diminuer la vigilance. Il s’agit ici de l’injection SQL.

La détection des injections SQL peut être relativement simple dans certains cas, mais l’exploitation (notamment pour récupérer des données de la base) demeure fastidieuse si elle est réalisée manuellement. C’est pourquoi l’outil SQLmap est particulièrement utile dans ce genre de situation.

Dans le contexte des WebSockets, un léger travail d’adaptation est nécessaire pour utiliser cet outil, car SQLmap n’a pas été conçu pour ce protocole.

Un script Python peut servir de proxy en ouvrant un serveur HTTP qui établira la connexion WebSocket. Voici un exemple de script pour mieux comprendre ce processus :

Script proxy HTTP
Script proxy HTTP

Lorsque le serveur reçoit une requête HTTP, une connexion WebSocket est établie sur l’endpoint que nous souhaitons tester; et la réponse HTTP correspondra au message envoyé par le serveur en réponse à celui du client.

Pour mieux comprendre les requêtes effectuées, un proxy a été configuré afin d’observer les communications à travers le Proxy history de Burp.

Ainsi, nous pouvons utiliser SQLmap de manière classique :

Ici, un tamper script est utilisé, car le serveur s’attend à recevoir le nom d’utilisateur et le mot de passe sous forme base64.

Ce script peut facilement être adapté à tous les endpoints que l’on souhaite tester ; il suffit de modifier l’URL, les noms des paramètres, ainsi que le format d’envoi et de réponse des données.

Concernant la vulnérabilité exploitée par SQLmap, la meilleure pratique de sécurité pour éviter ce type de faille est d’utiliser des requêtes préparées. Cette méthode est la plus efficace pour prévenir les injections SQL, indépendamment du protocole utilisé (HTTP ou WebSocket).

Conclusion

Le protocole WebSocket répond à un besoin de communication en temps réel. Cependant, il est utilisé principalement pour des fonctionnalités spécifiques et reste bien moins répandu que HTTP. Cela explique pourquoi les outils de test sont moins développés pour ce protocole.

Comme nous l’avons vu, les vulnérabilités rencontrées sont souvent similaires, voire identiques (comme dans le cas de l’injection SQL). Seules les techniques de détection et d’exploitation peuvent légèrement différer d’un protocole à l’autre.

Auteur : Julien BRACON – Pentester @Vaadata