
Détecte et exploite les vulnérabilités de contrebande de requêtes HTTP via la conversion HTTP/2 vers HTTP/1.1, en utilisant des techniques automatisées de contrebande d'en-têtes pour identifier les divergences d'analyse du serveur backend.
Cet outil aide à détecter et exploiter le smuggling de requêtes HTTP dans les cas où il peut être réalisé via la conversion HTTP/2 -> HTTP/1.1 par le serveur frontal.
Le schéma est le suivant :
L'attaquant veut trouver une telle requête qui sera vue comme deux requêtes distinctes par le serveur back-end.
Si la connexion HTTP/1.1 entre le frontal et le back-end utilise keep-alive, le frontal pourrait envoyer des requêtes d'autres utilisateurs sur la même connexion. Si nous parvenons à "empoisonner" la connexion avec une requête partielle qui suit une requête légitime, nous pouvons récupérer la requête d'un autre utilisateur.
D'autres scénarios possibles incluent le contournement de la protection et des réécritures du serveur frontal, l'empoisonnement du cache ou la tromperie du cache.
Pour plus d'informations sur le HTTP Request Smuggling, veuillez vous référer à Portswigger Web Security Academy.
En HTTP/2, tous les noms et valeurs des en-têtes HTTP sont binaires. Cela signifie que techniquement, ils peuvent contenir des espaces supplémentaires ou même des retours à la ligne.
RFC7540#10.3 stipule que les implémentations qui convertissent les requêtes HTTP/2 en HTTP/1 doivent tenir compte des limitations sur le jeu de caractères qui découlent d'une telle conversion ; la plupart des implémentations les rejettent effectivement. Malgré cela, nous espérons en trouver qui autorisent de tels en-têtes. Ils corrompront la requête HTTP/1.1 convertie vers le back-end.
Un autre point est que certaines corrections récentes liées au HTTP Request Smuggling pourraient n'être implémentées que pour les analyseurs HTTP/1.1.
En général, nous espérons qu'il existe des implémentations de HTTP/2 qui ne sont pas très au courant des recherches récentes sur le HTTP Request Smuggling en HTTP/1.1 et n'incluent pas les mesures d'atténuation correspondantes.
Étonnamment, oui !
J'ai trouvé une possibilité de faire passer en contrebande un en-tête avec un espace via Cloudflare, ouvrant ainsi une porte pour le smuggling entre Cloudflare et le client (dans le cas où le logiciel d'un client Cloudflare accepte et nettoie les noms d'en-têtes). Voici l'article de blog.
Il y a aussi un autre rapport de bug bounty qui n'est pas encore public. Il utilise le fait que les logiciels personnalisés ne filtrent pas les retours à la ligne dans les en-têtes HTTP2, et le smuggling se produit à 100% (je peux voir les requêtes des autres utilisateurs).
Cependant, je comprends que ce type de vulnérabilités doit être frustrant rare : contrairement à HTTP/1.1, il n'y a pas autant d'implémentations de HTTP/2, et la plupart sont conçues avec la sécurité à l'esprit, rejetant ainsi les en-têtes suspects ou invalides.
L'outil dispose d'une sous-commande qui tente de détecter automatiquement si une cible est vulnérable à l'attaque HTTP Request Smuggling. L'algorithme derrière cette fonctionnalité est décrit dans cette section.
Pour réaliser une attaque HTTP Request Smuggling, nous devons d'abord "faire passer en contrebande" un seul en-tête (soit Content-Length soit Transfer-Encoding). Cela signifie que nous devons envoyer un en-tête qui a) contrôle où le corps de la requête se termine et b) n'est pas traité par le frontal mais est traité par le back-end.
Cela est généralement réalisé en modifiant un en-tête d'une manière ou d'une autre : ajouter des espaces ou des tabulations à la fin de son nom, remplacer la valeur par une semi-équivalente, etc.
L'idée de base de l'algorithme de détection de vulnérabilité est de détecter si le serveur traite réellement un en-tête passé en contrebande comme s'il s'agissait de Content-Length ou Transfer-Encoding. Nous faisons cela en envoyant plusieurs requêtes : certaines avec des valeurs valides et d'autres avec des valeurs invalides pour l'en-tête. Ensuite, nous essayons de détecter s'il existe un moyen de distinguer les réponses de ces deux groupes.
C'est pourquoi la sortie de l'outil ne contient pas les mots "vulnérable/invulnérable" : il dit simplement s'il peut distinguer les réponses provenant des requêtes de ces deux groupes.
L'outil considère que deux ensembles de réponses HTTP sont distinguables si au moins l'une des deux conditions suivantes est remplie :
Les timeouts sont traités comme une valeur de code d'état unique et différente de toute autre ; ainsi, l'outil remplace le schéma classique de "détection par timing".
Considérons un exemple. Supposons que nous essayions de faire passer en contrebande l'en-tête transfer-encoding en remplaçant le tiret par un underscore.
S'il apparaît que le serveur répond avec le code 400 chaque fois que nous envoyons transfer_encoding:zalupa et se bloque lorsqu'il s'agit de transfer_encoding:chunked, nous pouvons dire que le serveur traite probablement l'en-tête comme la valeur pour le transfer-encoding. Théoriquement, il pourrait s'agir soit du serveur frontal, soit du serveur back-end.
Le premier cas n'est pas intéressant car nous pourrions de toute façon envoyer la version non passée en contrebande de l'en-tête, et le second est ce que nous recherchons. Comme tout se passe en HTTP/2, le premier cas est évitable la plupart du temps : le serveur HTTP/2 détermine la fin du corps de la requête d'une autre manière qui n'est pas liée aux en-têtes de la requête et ne s'attend jamais à ce que le corps soit au format chunké HTTP/1.1 (celui avec des longueurs de chunks hexadécimaux).
Les variations concrètes des techniques de détection sont :
Nous envoyons une version passée en contrebande de Transfer-Encoding: chunked (par exemple transfer_encoding:chunked) et différents corps : le valide est 0\r\n\r\n et l'invalide est 999\r\n.
Dans le cas où les réponses sont différentes, nous pouvons être certains que le serveur back-end reçoit et traite l'en-tête passé en contrebande. Il n'y a aucune raison pour que le frontal fasse cela : HTTP/2 n'utilise pas le format chunké que nous avons envoyé, donc il serait invalide.
Nous nous attendons à ce que le back-end se bloque (c'est-à-dire que la requête expire) pour les requêtes invalides car il attend l'arrivée de plus de données.
C'est la variante de détection la plus fiable : si le serveur se bloque lors de la lecture du corps, quelque chose a probablement mal tourné car il n'y a pas d'utilisation de l'encodage de transfert HTTP/1.1 dans une requête HTTP/2.
Nous envoyons à nouveau une version passée en contrebande de Transfer-Encoding et différents corps : 0\r\n\r\n comme corps valide et X\r\n\r\n comme corps invalide.
Le cas est le même que ci-dessus, mais au lieu de lire le corps, nous nous attendons à ce que le back-end le valide au moins.
Nous envoyons une version passée en contrebande de l'en-tête Content-Length avec les valeurs 1 et -1.
Les deux valeurs sont invalides du point de vue du frontal : il existe un autre mécanisme pour déterminer la longueur du corps en HTTP/2, et aucun corps de requête n'est réellement envoyé dans les deux cas. Si les réponses sont différentes, nous supposons que c'est le serveur back-end qui a analysé les en-têtes.