HTTP Request Splitting
Aussi communément appellé HTTP Request Smuggling, ce type particulier de faille est plutôt un problème d'implémentation de la part des serveurs Web, Firewalls ou serveurs proxy. Le but est d'injecter des requêtes spécialement conçues pour prendre parti de ces erreurs de conceptions ou du moins de non-respects de la RFC pour passer outre certaines protection et faciliter diverses attaques. Ces exploitations restent minoritaires car très dépendantes de la configuration des serveurs sous-jacents et très largement patchées, je vais donc me contenter d'en donner quelques exemples simples (ces exploitations se déclinent de plus de multiple façons).
En guise de premier exemple, traitons un exemple largement diffusé, le Cache Poisoning :
POST /nimporte_quelle_page.html HTTP/1.1\r\n
Host: netpratic.com\r\n
Connection: Keep-Alive\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 0\r\n
Content-Length: 73\r\n\r\n
GET /fausse_page.html HTTP/1.1\r\n
Host: netpratic.com\r\n
User-Agent: GET /page_empoisonnee.html HTTP/1.1\r\n
Host: netpratic.com\r\n
Connection: Keep-Alive\r\n\r\n
Dans cet exemple, l'attaquant essaie d'atteindre une page sur netpratic.com. Or, il sait qu'au préalable il y a un serveur proxy qui cache les requêtes (pour gagner en temps de réponse). Ce type d'attaque s'appuie sur les différences possibles d'interprétation de la première requête GET erronnée (ayant deux Content-Length). Certains serveurs vont rejeter la requête, d'autres vont ignorer le deuxième Content-Length, d'autres encore ne vont garder que le dernier. De cette façon, en imaginant que le serveur proxy utilise le dernier tandis que le serveur Web utilise le premier, voici ce qui arrive :
- - les requêtes arrivent au proxy. Il ignore le premier Content-Length. Pour lui, la première requête est un post avec 73 bytes de données (soit exactement le nombre de bytes entre le premier GET et le deuxième). Ensuite, il traite une deuxième requête qui est GET /page_empoisonnee.html.
- - les paquets arrivent au serveur web. Il ignore le deuxième header. La première requête est donc un post sans données. Il traite ensuite sa deuxième requête, un GET sur fausse_page.html (qui contient GET /page_empoisonnee.html en tant que User-Agent et deux headers Host, ce qui n'est pas très grave puisqu'il s'agit du même Host).
- - les réponses reviennent au proxy et celui-ci les cache (les sauvegarde en mémoire). L'important ici est que le serveur va cacher ce qu'il retiendra comme page_empoisonnee.html qui sera en réalité fausse_page.html.
En considérant de plus que certains proxy peuvent atteindre des sites distants (avec un GET http://attacker_site/bad_page), le poisoning peut devenir bien plus qu'une simple nuisance. Ceci dit, bien que cette attaque soit jolie est que le proxying/caching soit désormais un élément important de la publication de gros sites Web, il faut bien noter que les serveurs les plus répandus (IIS et Apache) rejettent ce genre de paquets (deux headers similaires avec des valeurs différentes). Certains proxys et back-ends populaires sont tout de même vulnérables (comm Squid ou Tomcat). Dans le même esprit et sans nécessairement utiliser deux Content-Length mais des particularités de parsing de firewalls ou de proxys, il est possible de faire passer des requêtes qui seraient bloquées autrement (faire en sorte que les parties à cacher ne soient pas parsées correctement par le firewall) ou d'imbriquer des requêtes de manière similaire à cet exemple de façon à ce que proxy et serveur web ne voient pas la même requête (l'un considérant le troisième GET comme un header du second, l'autre considérant le second comme incomplet et traitant à la place le troisième).
Nous allons maintenant étudier une variante assez intéressante, connue sous le nom deRequest Hijacking :
POST /bla.php HTTP/1.1\r\n
Host: netpratic.com\r\n
Connection: Keep-Alive\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 0\r\n
Content-Length: 120\r\n\r\n
GET /change_password.php?newpassword=h4ck3d HTTP/1.1\r\n
Host: netpratic.com\r\n
Connection: Keep-Alive\r\n
Junk-Header:
Lorsque que l'attaquant va envoyer cette requête au serveur Web, toujours derrière un serveur proxy qui ne traite pas les headers de la même façon, le serveur Web va traiter le deuxième GET comme incomplet. Comme les requêtes de tous les clients arrivent par le même canal (le proxy), on peut imaginer qu'un autre client identifié, la victime, demande alors une autre page. Du point de vue du serveur, après la requête POST, il traite alors la seconde requête GET, enfin complète :
GET /change_password.php?newpassword=h4ck3d HTTP/1.1\r\n
Host: netpratic.com\r\n
Connection: Keep-Alive\r\n
Junk-Header: GET /my_account.php HTTP/1.1\r\n
Host: netpratic.com\r\n
User-Agent: my-favorite-browser\r\n
Cookie: PHPSESSID=9815671346BE24\r\n\r\n
De cette manière, l'attaquant aura modifié le mot de passe de la victime en utilisant ses cookies de session, valides. Cette attaque est très proche du CSRF (Cross-Site Request Forgery, le fait d'envoyer à des victimes des URL leur faisant faire des actions à leur insu sur un site où elles sont identifiées), mais demeure plus puissante car elle résiste par exemple à certaines protections contre le CSRF comme les tokens. On peut en retrouver des variantes où l'attaquant se sert d'un XSS sur une page du site pour le faire exécuter automatiquement à un client. Certaines techniques permettent même une alternative à XST (voire article suivant) pour capter les cookies HTTPOnly.
D'autres particularités de parsing (troncation de données, acceptation des GET avec Content-Length) peuvent permettre d'autres variantes de ces attaques. Nous allons maintenant nous intéresser à une faille plus applicative, concernant non plus les requêtes mais les réponses HTTP.
HTTP Response Splitting
Cette vulnérabilité dans son exploitation est semblable à un XSS ou à un CSRF : caché dans un autre site malveillant, persistent dans une page empoisonnée ou tout simplement passé comme lien via un spam ou par ingénieurie sociale. Un attaquant un peu dépendant de sa victime donc, mais vous le savez, cela reste rarement un problème... Cette faille est finalement dans sa nature une sorte de XSS, mais en plus rare et plus puissant à la fois. En fait, au lieu de réussir à injecter des données dans le corps d'une page, le but est de réussir à insérer des données dans les headers HTTP, plus particulièrement à insérer l'alliage Carriage Return + Line Feed (CRLF), communément dénotés \r\n. De cette manière, l'attaquant aura le contrôle des headers de la requête de donc de la réponse entière qui sera présentée à la victime, assez puissant en effet. Certains interpréteurs comme PHP ont naturellement une protection contre ces attaques en urlencodant systématiquement les headers qu'ils rendent au serveur Web. Pour illustrer cette vulnérabilité, nous allons donc considérer le script CGI basique suivant, écrit en perl :
#!/usr/bin/perl
use CGI qw(:standard);
print "Content-Type: text/html\n";
print "Set-Cookie: ",param('name'),"\n\n";
print "\n";
print "
print "\n";
print "\n";
print "
Salut, ",param('name'),"
\n";
print " \n";
Il est assez aisé de le comprendre, il prend s'implement le paramètre "name" qu'il reçoit, le transpose sur la page (de façon totalement non sécurisée mais là n'est pas le l'objet de notre discussion) et le sauvegarde dans un cookie pour se souvenir du nom du client à l'avenir.
Forts de notre connaissance des HTTP Response Splitting, on peut spécifier un nom nous permettant de recréer les headers puis d'afficher le contenu de notre choix. Essayons donc avec comme nom bla%0D%0AContent-Type: text/html%0D%0A%0D%0A--><html><h1>This page was Hacked !!</h1></html><!-- (sachant que \r est codé 0x0D et \n 0x0A) :
Comme vous le voyez, la page est ainsi défacée. Un petit coup d'oeil au code source (toujours dans le lien ci-dessus) nous permet de comprendre cet affichage et pourquoi le message original n'est pas présent.
Bien sûr, il y a dans notre cas des aléas de présentation (doublons, etc.) qui peuvent être gommés (ou masquer si on présente une belle fausse page suffisamment longue ou en travaillant la présentation par scripting). De plus, il est tout à fait possible d'accoler une nouvelle réponse HTTP après cette première réponse modifiée, car les navigateurs traditionnels se font une joie d'accoller le contenu de la deuxième réponse au contenu de la première. L'important ici est que nous avons réussi à créer une page totalement différente et à supprimer le contenu original. Autrement dit, l'attaquant a ici pleinement contrôle des données présentées et peut y injecter des scripts malveillants ou proposer une page semblable avec des liens détournés (phishing).
Toujours exploitable par response splitting, les redirections dynamiques où l'utilisateur a un contrôle sur la redirection (on imagine par exemple des redirections dans ./old/ + param('page') pour assurer la compatibilité descendante). Dans ce cas, l'exploitation est identique, aux détails près que l'injection se fait dans le header Location et que le code de retour est un 30x et non pas un 200 (il faut donc rediriger vers une page malveillante ou juxtaposer un retour 200 OK).
Ceci conclut donc cette section sur ces attaques particulières que sont la famille des HTTP Splitting, qui tentent de prendre à leur avantage le protocole du même nom. Toujours dans l'optique d'utiliser le serveur Web, nous allons désormais étudier le Cross-Site Tracing qui se présente comme une facilitation de l'exploitation des XSS.