Skip to main content

Démystifier le désynchronisation HTTP

Écrit par
Headshot of Sam Sanoop

Sam Sanoop

prioritize vulns

30 juin 2020

0 minutes de lecture

Le smuggling de requêtes HTTP est un type de vulnérabilité intéressant qui a gagné en popularité au cours de l’année écoulée. Cette vulnérabilité peut permettre à un attaquant d’exploiter certaines caractéristiques du protocole HTTP/1.1 afin de contourner les protections de sécurité, de mener des attaques de phishing et d’obtenir des informations sensibles issues de requêtes autres que les siennes.

Il convient également de noter que le smuggling de requêtes a suscité un vif intérêt au sein de la communauté, avec de nombreux rapports de bug bounty très rémunérateurs ces derniers mois. Snyk a par ailleurs observé que 18 vulnérabilités liées au smuggling de requêtes HTTP dans des dépendances avaient été publiées en 2020 à ce jour.

Cette vulnérabilité a été abordée pour la première fois par Watchfire dans son livre blanc de 2005 intitulé « HTTP Request Smuggling ». Ces travaux ont ensuite été approfondis par le chercheur Regis Leroy, puis par James Kettle, de l’équipe de sécurité de Portswigger, lors de la conférence BlackHat USA 2019, ce qui lui a valu une attention médiatique accrue.

Cet article vise à démystifier cette vulnérabilité et à fournir aux responsables de projets open source des conseils de correction pour éliminer le smuggling de requêtes HTTP de leurs projets.

Contexte

À mesure que de nouvelles attaques et vulnérabilités gagnent en popularité et attirent l’attention des médias, les bibliothèques open source sont souvent ciblées par des attaquants qui cherchent à les exploiter dans des conditions réelles. En raison du manque de connaissances sur les mesures correctives, il faut souvent des semaines, voire des années, pour corriger ces vulnérabilités dans les bibliothèques concernées. Pour sécuriser la communauté open source, l’équipe Snyk Security mène régulièrement des recherches sur les vulnérabilités, notamment le smuggling de requêtes HTTP, afin de détecter celles qui touchent les dépendances open source et de fournir aux responsables de leur maintenance des conseils concrets pour les corriger.

Snyk a notamment cherché à déterminer l’impact du smuggling de requêtes HTTP sur l’écosystème des dépendances open source, et a ainsi découvert de nombreuses vulnérabilités. Vous trouverez ici plus d’informations sur les vulnérabilités découvertes.

Snyk a constaté qu’il existe trop peu de ressources publiques fournissant des conseils adaptés pour corriger le smuggling de requêtes HTTP. En outre, les responsables de la maintenance ont parfois dû publier plusieurs versions avant de corriger et de résoudre correctement cette vulnérabilité. Avec cet article, nous souhaitons combler cette lacune et fournir des conseils détaillés à toutes les personnes responsables de la maintenance qui souhaitent savoir comment traiter ce problème.

Explication de la vulnérabilité de smuggling de requêtes HTTP

Pour comprendre le smuggling de requêtes HTTP, il faut d’abord connaître les éléments suivants :

Keep-Alive et pipelining

Le concept de keep-alive et de pipelining a été publié initialement dans la RFC 2616.

L’en-tête Keep-Alive est un en-tête hop-by-hop qui fournit des informations sur une connexion persistante. Dans les serveurs Web, Keep-Alive peut être spécifié dans l’en-tête « Connection », ce qui permet au serveur Web de maintenir une socket/connexion TCP ouverte. Cet en-tête permet d’utiliser une seule connexion pour plusieurs requêtes et réponses, ce qui peut réduire la surcharge et améliorer les performances d’un serveur Web. Cette fonctionnalité est prise en charge par tous les navigateurs et serveurs actuels.

Le pipelining est une autre fonctionnalité introduite dans la RFC 2616. Elle permet à un serveur Web de traiter les requêtes de manière asynchrone, sous la forme d’un flux premier entré, premier sorti, plutôt que de traiter chaque requête individuellement. Le serveur peut ainsi envoyer une requête sans attendre la réponse à la précédente.

Content-Length et Transfer-Encoding

Les requêtes HTTP peuvent contenir un corps de message. La présence de ce corps dans une requête est indiquée par un champ d’en-tête Content-Length ou Transfer-Encoding. Ces en-têtes servent au cadrage des messages : ils indiquent au serveur où se termine un message et où commence le suivant.

Content-Length, défini dans la RFC 7230, section 3.3.2, est un en-tête HTTP qui indique la taille du corps de la requête. On le retrouve couramment dans les requêtes HTTP POST, qui contiennent des données dans leur corps. À noter que les requêtes GET ne devraient généralement pas contenir l’en-tête Content-Length, puisqu’elles n’ont pas de corps.

Transfer-Encoding, également défini dans la RFC 7230, a été créé pour permettre l’envoi de données binaires sur HTTP. Transfer-Encoding comporte plusieurs directives ; cet article se concentre sur la directive chunked.

La directive chunked permet d’envoyer les données sous la forme d’une série de blocs, accompagnés de la longueur de ces blocs indiquée au format hexadécimal, puis d’un retour chariot et d’un saut de ligne. La directive chunked prend fin avec 0 suivi d’une séquence vide. Vous trouverez ci-dessous un exemple de requête chunked.

POST / HTTP/1.1
Host: snyk.io
Content-Type: application/x-www-form-urlencoded
Transfer-Encoding: chunked

7(length of chunks)
foo=bar(series of chunks)
0(0 to terminate request followed by rn)
(rn)

Smuggling de requêtes

Une pile de serveurs Web moderne comprend souvent plusieurs serveurs Web, des équilibreurs de charge et des serveurs WSGI. Voici un schéma simple pour illustrer cela :

Schéma montrant un utilisateur se connectant à une application via un répartiteur de charge frontal ou un proxy, puis un middleware backend, WSGI ou un reverse proxy.

Les vulnérabilités de smuggling de requêtes HTTP apparaissent lorsque le frontal et le serveur dorsal n’interprètent pas de la même manière les limites d’une requête HTTP, ce qui entraîne une désynchronisation entre eux. Cela s’explique par le fait que de nombreuses bibliothèques frontales et dorsales s’écartent des spécifications RFC lorsqu’elles traitent les en-têtes Content-Length et Transfer-Encoding. Le corps des requêtes HTTP peut être délimité à l’aide de ces deux en-têtes, et des écarts par rapport aux spécifications peuvent se produire. Une partie d’une requête est alors ajoutée à la requête suivante ou dissimulée dans celle-ci, ce qui permet à un autre utilisateur de recevoir la réponse à la requête dissimulée.

Cette vulnérabilité peut être exploitée pour mener des attaques de phishing, empoisonner le cache, réaliser du cross-site scripting (XSS), et bien plus encore. James Kettle a publié l’année dernière de plus amples informations sur l’exploitation de cette vulnérabilité lors de BlackHat USA 2019, dans une présentation intitulée « HTTP Desync Attacks: Request Smuggling Reborn ». Cet article se concentre sur les deux techniques de smuggling de requêtes les plus courantes :

  • CL:CL : technique d’attaque par double Content-Length

  • CL:TE : Content-Length et Transfer-

    Encoding : technique d’attaque

CL:CL : technique d’attaque par double Content-Length

Selon la RFC 7230, section 3.3.3#4 :

« Si un message est reçu sans Transfer-Encoding et avec plusieurs champs d’en-tête Content-Length dont les valeurs diffèrent, ou avec un seul champ d’en-tête Content-Length dont la valeur est invalide, le cadrage du message est invalide et le destinataire DOIT le traiter comme une erreur irrécupérable »

Cependant, la plupart des intergiciels et des serveurs Web traitent actuellement les requêtes GET contenant un corps avec une certaine tolérance. En outre, la RFC 7231, section 4.3#4.3.1 indique : « La charge utile d’un message de requête GET n’a pas de sémantique définie ; l’envoi d’un corps de charge utile dans une requête GET peut amener certaines implémentations existantes à rejeter la requête ». Cela indique que la plupart des serveurs et mandataires sont susceptibles de prendre ce comportement en charge. Dans certains cas, cela peut entraîner des attaques par smuggling de requêtes. Cet article examine une variante de cette attaque.

GET / HTTP/1.1
Content-Length: 43
Content-Length: 0
Host: snyk.io

GET /reqsmuggle HTTP/1.1
Host: snyk.io

Lorsque deux en-têtes Content-Length sont présents, des attaques par smuggling peuvent se produire si le frontal et le serveur dorsal ne s’accordent pas sur l’en-tête Content-Length à privilégier.

Schéma illustrant une attaque par désynchronisation HTTP entre un attaquant, un utilisateur, un répartiteur de charge, un proxy inverse et un serveur d’applications.

Dans la requête HTTP ci-dessus, une requête contenant deux en-têtes Content-Length est envoyée à une cible dont le frontal est un mandataire ou un équilibreur de charge. Le mandataire respecte le premier en-tête Content-Length et le privilégie ; il considère donc la requête dissimulée comme faisant partie du corps de la requête, même si une requête GET ne devrait pas avoir de corps et que deux en-têtes Content-Length sont présents. Lors du traitement par le serveur dorsal, le premier en-tête Content-Length est ignoré et le second est privilégié. Comme la valeur du second en-tête Content-Length est zéro, le serveur dorsal n’attend aucun corps de requête et traite la requête /reqsmuggle comme une autre requête pipelinée. Un autre utilisateur peut donc recevoir la réponse à cette requête dissimulée.

HTTP/1.1 200 OK
Content-Length: 11

hello worldHTTP/1.1 404 Not Found
Connection: Close
Content-Length: 0

CL:TE : technique d’attaque Content-Length/Transfer-Encoding

Cette technique répandue, découverte par James Kettle, de PortSwigger Labs, consiste à placer un en-tête Content-Length et un en-tête Transfer-Encoding dans une même requête HTTP et à les manipuler de sorte que le mandataire frontal et le serveur dorsal privilégient ces en-têtes différemment et traitent la requête dissimulée de façons distinctes. Voici un exemple de requête qui illustre cette vulnérabilité.

POST /login HTTP/1.1
Host: snyk.io
Content-Type: application/x-www-form-urlencoded
Content-Length: 62
Transfer-Encoding: chunked

16
login=xxx&password=xxx
0

GET /404 HTTP/1.1
X-Foo: bar

Lorsque le mandataire frontal traite cette requête, il respecte l’en-tête Content-Length et le privilégie par rapport à l’en-tête Transfer-Encoding. Le serveur dorsal, quant à lui, privilégie l’en-tête Transfer-Encoding. Il traite donc la requête jusqu’au caractère 0. La requête GET 404 est alors considérée comme une requête pipelinée distincte.

Ce problème se produit parce que le frontal privilégie l’en-tête Content-Length par rapport à l’en-tête Transfer-Encoding. Selon la section 3.3.3#3 de la RFC 7230, si un message contenant à la fois Content-Length et Transfer-Encoding est accepté, l’en-tête Transfer-Encoding prévaut sur Content-Length. Le frontal ne respecte pas cette règle.

Schéma illustrant une attaque HTTP Content-Length : une requête POST fragmentée de l’attaquant dissimule une requête GET qui traverse un répartiteur de charge et un serveur backend jusqu’à une application.

Lorsque le frontal est susceptible de privilégier l’en-tête Transfer-Encoding, il peut être possible de contourner cette limitation en insérant un en-tête de transfert encodé malformé plutôt qu’un en-tête valide. Voici quelques exemples :

Transfer-Encoding : chunked
tRANSFER-ENCODING: chunked
Transfer-Encoding: chunk

De nombreuses bibliothèques HTTP tolèrent différentes variantes de l’en-tête Transfer-Encoding et les normalisent afin d’améliorer l’expérience client. En déterminant les variantes de l’en-tête Transfer-Encoding que le serveur dorsal normalise, il peut être possible de faire passer un en-tête Transfer-Encoding malformé par le frontal et de mener une attaque par smuggling CL:TE.

Les exemples ci-dessus illustrent simplement la vulnérabilité. Dans le cadre d’une attaque réelle, une requête dissimulée peut être conçue pour mener des attaques de phishing ou voler les données de la requête d’une victime afin de les envoyer à un serveur contrôlé par un attaquant.

Périmètre et idées reçues

Au cours de cette recherche, Snyk a relevé de nombreux cas où la complexité manifeste des multiples vecteurs d’attaque a donné lieu à des idées reçues au sujet du smuggling de requêtes HTTP. Des chercheurs et des responsables de la maintenance en ont signalé à des responsables de projets open source sans toujours comprendre si les corrections requises relevaient du périmètre de la bibliothèque dont ils avaient la charge ou s’il fallait plutôt les apporter à une dépendance utilisée par cette bibliothèque.

Voici quelques points qui nécessitaient des éclaircissements supplémentaires :

  1. Les mandataires, les équilibreurs de charge et les autres composants pouvant être classés comme frontaux sont les plus touchés par cette vulnérabilité. Toutefois, la réussite d’une attaque par smuggling de requêtes nécessite l’exploitation d’un frontal et d’un serveur dorsal vulnérables.

  2. La responsabilité de la correction incombe autant aux responsables de la maintenance du serveur dorsal qu’à ceux du frontal. On pourrait faire valoir qu’un serveur dorsal devrait normaliser les en-têtes malformés et tolérer davantage les erreurs des utilisateurs, et que le véritable problème réside dans les mandataires qui transmettent ces requêtes sans les normaliser au préalable. Toutefois, compte tenu de la grande diversité de l’écosystème des dépendances et du nombre de bibliothèques pouvant jouer le rôle de serveur dorsal ou frontal, les deux parties devraient idéalement tenter de corriger le problème.

  3. Les intergiciels, les serveurs Web prenant en charge les fonctions de mandataire inverse et les serveurs WSGI/haute performance répondent aux critères d’un serveur dorsal. Ce n’est pas le cas d’un serveur Web qui se contente de traiter les requêtes malformées sans prendre en charge le pipelining.

  4. Les rapports de sécurité signalent souvent des problèmes de smuggling de requêtes HTTP parce que des serveurs répondent à plusieurs requêtes envoyées, et que ces réponses apparaissent comme deux réponses distinctes. À noter que de nombreux serveurs prennent en charge Keep-Alive et le pipelining ; cela ne suffit pas, à lui seul, à constituer une vulnérabilité de smuggling de requêtes HTTP. C’est le cas de la CVE-2020-12440 signalée pour NGINX.

Correction

Les problèmes de désynchronisation des requêtes HTTP sont dus à des écarts par rapport aux spécifications HTTP actuelles et au fait que plusieurs bibliothèques ne respectent pas la RFC 7230. De plus, la correction de cette vulnérabilité peut s’avérer délicate selon que vous maintenez un projet côté frontal ou côté serveur. Snyk a donc étudié les mesures correctives actuellement mises en œuvre par des projets open source et les a classées comme suit. Pour simplifier, chaque mesure est présentée avec une explication du type d’attaque par désynchronisation qu’elle permet de contrer. Dans l’idéal, toutes les mesures ci-dessous devraient être appliquées afin d’assurer une défense en profondeur.

Donner la priorité à Transfer-Encoding plutôt qu’à Content-Length — Mesure corrective :Cette mesure permet de prévenir les attaques CL:TE et TE:CLPérimètre : Front-end, back-endDétails :Lorsqu’une requête contenant à la fois un en-tête Transfer-Encoding: chunked et un en-tête Content-Length est reçue, l’en-tête Transfer-Encoding doit avoir priorité sur Content-Length. Ce comportement est défini dans la section 3.3.3#3 de la RFC 7230.

Rejeter les requêtes contenant à la fois Content-Length et Transfer-Encoding, ainsi que celles avec plusieurs en-têtes Content-Length — Mesure corrective : Cette mesure permet de prévenir les attaques CL:CL, CL:TE et TE:CLPérimètre : Back-end, front-end, back-end, bibliothèques en amontDétails :Cette solution peut être considérée comme une meilleure option que « Donner la priorité à Transfer-Encoding plutôt qu’à Content-Length ». Des environnements d’exécution tels que Node.js ont adopté cette solution pour contrer la désynchronisation des requêtes : toute requête contenant les deux en-têtes reçoit une réponse HTTP 400.

Snyk estime que cette technique constitue une solution idéale pour prévenir les problèmes de désynchronisation. Il convient toutefois de réfléchir à la manière dont la correction est appliquée, en particulier si elle concerne une bibliothèque HTTP de bas niveau ou un moteur en amont dont dépendent de nombreux packages, comme un frontal. 

Pour corriger les problèmes de désynchronisation des requêtes HTTP, il convient de suivre la RFC 7230 #3.3.3. La section 3.3.3#3 de la RFC 7230 indique :

« Si un message est reçu avec un champ d’en-tête Transfer-Encoding et un champ d’en-tête Content-Length, Transfer-Encoding prévaut sur Content-Length.  Un tel message peut indiquer une tentative de désynchronisation des requêtes (section 9.5) ou de fractionnement des réponses (section 9.4) et doit être traité comme une erreur.  Un expéditeur DOIT supprimer le champ Content-Length reçu avant de transférer le message en aval. »

Cela diffère toutefois de ce qu’indique la RFC 2616 4.4#3 :

« Si un message est reçu avec un champ d’en-tête Transfer-Encoding et un champ d’en-tête Content-Length, ce dernier DOIT être ignoré. »

Il convient de noter que la RFC 2616 4.4#3 est obsolète et a été remplacée par la RFC 7230. Il faut tenir compte de la RFC 7230 lors de la mise en œuvre d’une correction. Ainsi, lorsqu’une requête HTTP contient à la fois un en-tête Content-Length et un en-tête Transfer-Encoding, le comportement correct consiste pour le frontal à supprimer l’en-tête Content-Length avant de transmettre la requête à un serveur en aval, et non à renvoyer une réponse HTTP 400. Il faut procéder de la même manière pour les requêtes contenant plusieurs en-têtes Content-Length, comme le précise la RFC 7230#3.3.2.

Comme indiqué dans la section 3.3.2 de la RFC 7230, si une requête HTTP reçue contient plusieurs en-têtes Content-Length avec des valeurs différentes, il est possible de renvoyer une réponse HTTP 400 ou de remplacer les valeurs de champ dupliquées par un seul champ Content-Length valide. Snyk recommande aux bibliothèques HTTP de bas niveau de remplacer les en-têtes multiples par un seul en-tête valide.

Rejeter les en-têtes Transfer-Encoding mal formés et traiter correctement plusieurs valeurs TE — Mesure corrective :Cette mesure permet de prévenir les attaques TE:TE.Périmètre : Front-end, back-endDétails :Si le frontal et le back-end donnent tous deux la priorité à l’en-tête Transfer-Encoding, cela peut permettre des attaques par désynchronisation où un attaquant insère deux en-têtes Transfer-Encoding : l’un est ignoré par le frontal et traité par le back-end, ou inversement. Les variantes d’en-tête suivantes doivent donc être rejetées.

Tableau des variantes de l’en-tête Transfer-Encoding et détails sur la manière dont les serveurs normalisent ou gèrent chacune d’elles.

Il convient toutefois de noter que des attaquants peuvent toujours trouver une variante mal formée de l’en-tête utilisant l’encodage « chunked » qui n’est pas documentée ci-dessus. Cette mesure ne suffit donc pas, à elle seule, à corriger la vulnérabilité. Une meilleure approche consiste à rejeter les requêtes contenant à la fois Content-Length et Transfer-Encoding, ainsi que les en-têtes mal formés.

Enfin, il convient également de noter que si la valeur « chunked » est spécifiée avec d’autres valeurs Transfer-Encoding, telles que « gzip » ou « deflate », il ne faut pas la négliger et Transfer-Encoding doit avoir priorité sur Content-Length. La CVE-2019-16786 est un exemple de cette vulnérabilité. Si une requête HTTP est envoyée avec les valeurs Transfer-Encoding suivantes, la valeur « chunked » doit être correctement détectée et priorisée :

Transfer-Encoding: gzip, chunked

Plusieurs valeurs peuvent être indiquées dans un en-tête Transfer-Encoding, séparées par une virgule. Cependant, il peut arriver qu’un frontal ne détecte que la valeur « gzip » et donne ainsi la priorité à l’en-tête Content-Length, tandis que le back-end traite la requête comme une requête chunked, ce qui entraîne une attaque CL:TE. Snyk recommande de vérifier correctement les requêtes comportant plusieurs valeurs et, si « chunked » est spécifié, de donner la priorité à Transfer-Encoding. De plus, lorsque plusieurs en-têtes Transfer-Encoding sont spécifiés, la valeur « chunked » ne doit apparaître qu’en dernier, après les autres valeurs Transfer-Encoding. C’est ce qu’indique la section 3.3.1 de la RFC 7230.

Pour conclure

En conclusion, la désynchronisation des requêtes HTTP est une vulnérabilité qui peut être difficile à comprendre et à corriger. Cet article vise à aider les mainteneurs à écrire des correctifs efficaces et à sécuriser leurs projets open source.

Il convient également de noter que cet article ne présente que deux techniques de désynchronisation des requêtes HTTP et ne détaille pas les différents scénarios d’exploitation. L’article suivant du chercheur en sécurité ZeddYu traite en détail de la désynchronisation HTTP et présente chaque vecteur d’attaque : Zeddy Yu : Vous aider à comprendre la désynchronisation HTTP en un article.

Lancez-vous dans les compétitions Capture The Flag

Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.

Lire la suite

Blog

Les modèles de pointe ont trouvé les vulnérabilités. Seul l’attaquant a trouvé les chaînes d’exploitation.

L’analyse statique a détecté les failles, mais seuls des tests d’attaque en conditions réelles ont prouvé comment elles pouvaient être enchaînées pour provoquer des compromissions. Comparaison d’Evo COS, de Claude Security et de Claude Code Security.

feature insights context
Blog

Les attaques autonomes sont déjà là. La défense doit suivre leur rythme.

Les attaquants autonomes réduisent la fenêtre de défense. Découvrez comment la découverte, la correction, la validation et la prévention continues peuvent aider les équipes de sécurité à suivre le rythme.

Blog

Votre backlog de vulnérabilités n’est plus une dette technique, c’est une surface d’attaque

Un backlog de vulnérabilités qui s’allonge est plus qu’une dette technique : c’est une surface d’attaque. Découvrez pourquoi les anciennes hypothèses de risque, les attaquants automatisés et les vulnérabilités en chaîne exigent une nouvelle approche.