July 2011 Archives

lundi 25 juillet 2011, 14:50:16 (UTC+0200)

Audit partiel des composants Open Source intégrés à VMware ESX (SLP)

Cet article est la deuxième partie d'une étude partielle des composants Open Source intégrés aux produits VMware. La partie précédente, traitant de WBEM et SFCB est accessible ici. Cette fois, le focus se fera sur le composant SLP (pour Service Location Protocol).


SLP permet aux différents équipements d'un réseau de connaitre les services offerts sur ce réseau. Typiquement, un équipement agit en tant que client (dit User Agent ou UA) et/ou en tant que serveur (dit Service Agent ou SA). Dans les gros déploiements, des équipements additionnels (Directory Agents ou DA) permettent de limiter le trafic échangé. Les communications utilisent UDP et le port 427, à moins que les données soient trop grandes pour un seul paquet, auquel cas TCP est employé (sur le même port). Les annonces et les recherches sont usuellement réalisées en multicast. La version 1 de ce protocole est définie par la RFC 2165 alors que la version 2 l'est par la RFC 2608.


Les produits VMware ESX, tout comme Novell eDirectory, utilisent l'implémentation de référence OpenSLP, connue pour tourner sous de nombreux OS dont Windows, Linux, FreeBSD, MacOS X, Solaris, ... Le logiciel est distribué sous licence Caldera (similaire à la licence BSD), ce qui permet son utilisation en partie ou en totalité dans des logiciels propriétaires. Ce point sera à garder en mémoire si on souhaite maintenir une liste des implémentations vulnérables. La version utilisée lors des tests est la 1.2.1, qui fut publiée en *2005* afin d'intégrer les correctifs de sécurité proposés par l'équipe de SUSE suite à son audit de code.


Afin de rester dans le scénario du pari (compromission depuis le réseau d'administration), les routines de parsing des paquets reçus furent les premières examinées. L'équipe SUSE avait bien bossé, les nombreux offsets étant systématiquement validés avant usage. Enfin de cibler facilement le code exercé par des paquets malformés, l'extension lcov (un front-end graphique à l'outil de couverture de code gcov fourni avec GCC) fut utilisée :



Il apparut rapidement que la fonction de traitement des extensions SLP v2 présentait un problème classique de parcours de liste chainée :

850	static int v2ParseExtension(SLPBuffer buffer, SLPMessage * msg)
851	{
852	/*  0                   1                   2                   3
853	    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
854	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
855	   |         Extension ID          |       Next Extension Offset   |
856	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
857	   | Offset, contd.|                Extension Data                 \
858	   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */
859	
860	   int result = 0;
861	   int nextoffset = msg->header.extoffset;
862	   while (nextoffset)
863	   {
864	      int extid;
865	      buffer->curpos = buffer->start + nextoffset;
866	      if (buffer->curpos + 5 > buffer->end)
867	         return SLP_ERROR_PARSE_ERROR;
868	
869	      extid = GetUINT16(&buffer->curpos);
870	      nextoffset = GetUINT24(&buffer->curpos);
871	      switch (extid)
872	      {
                 [ ... encore du code ... ]
892	      }
893	   }
894	   return result;
895	}

Ce qui peut être interprété comme suit :

  • Si la valeur de msg->header.extoffset est différente de zéro (ligne 862) alors on avance jusqu'à cette extension (ligne 865)
  • Une erreur est levée si le buffer ne contient pas de quoi stocker une extension de taille minimale, soit 5 octets (ligne 867)
  • L'extension courante est traitée (ligne 871) et le traitement se poursuit si nextoffset est différent de zéro (ligne 862)


Cette section de code contient une vulnérabilité de type déni de service, saurez-vous trouver laquelle ?

La réponse est donnée quelques lignes plus bas ...

.
.
.
.
.
.
.

Cette vulnérabilité est liée à l'utilisation de nextoffset, qui est un pointeur relatif au début du paquet : une boucle infinie sera créée si une extension pointe vers elle-même ou vers une extension précédente (CVE-2010-3609). En effet, un invariant implicite n'est jamais vérifié. Cet invariant pourrait être exprimé sous la forme suivante : "le pointeur vers l'extension suivante doit être supérieur ou égal au pointeur vers l'extension courante additionné de la taille minimale d'une extension (5 octets)". Pour autant, l'implémentation de cet invariant n'a pas été retenu par les développeurs OpenSLP, ceux-ci optant pour une solution amha bien plus compliquée, consistant à construire et vérifier une liste d'extensions (cf. diff coloré). Mais bon, le correctif fait bien son travail, donc on ne va pas chipoter sur l'élégance de la chose.


Les plus astucieux auront détecté (lignes 865 et 866) quelque chose ressemblant étrangement à un débordement d'entier. Bien vu ! Sauf que la variable buffer->curpos à laquelle on ajoute 5 vient du champ nextoffset qui est sur seulement 3 octets. Donc, à moins que buffer->start (que l'attaquant ne contrôle pas) soit supérieur à 0xFF000000, cela n'est pas exploitable.


Côté correctifs :

  • La version stable d'OpenSLP n'inclut pas encore de correctif, neuf mois après la correction du trunk
  • VMware a publié des correctifs pour ESXi et ESX v4
  • Novell semble s'être contenté de proposer des correctifs pour les différentes versions de SUSE Linux, oubliant (ou patchant silencieusement) son annuaire eDirectory
  • SUSE utilise son propre patch, bien plus simple que celui d'OpenSLP (10 lignes au lieu de 60)
  • Ce patch Suse a été réutilisé par Ubuntu et le sera par RedHat et Fedora


Petit zoom sur le patch écrit par SUSE :

--- openslp-dfsg-1.2.1.orig/common/slp_message.c
+++ openslp-dfsg-1.2.1/common/slp_message.c
@@ -872,10 +872,19 @@
     int             extid;
     int             nextoffset;
     int             result  = SLP_ERROR_OK;
+    int             bufsz = (int)(buffer->end - buffer->start);
 
     nextoffset = message->header.extoffset;
     while(nextoffset)
     {
+        /* check for circular reference in list
+         * if the size gets below zero, we know we're
+         * reprocessing extensions in a loop.
+         */
+        bufsz -= 5;
+        if (bufsz <= 0) 
+            return SLP_ERROR_PARSE_ERROR;
+
         buffer->curpos = buffer->start + nextoffset;
         if(buffer->curpos + 5 >= buffer->end)
         {


Le CERT-US a été contacté afin de faciliter la synchronisation entre éditeurs, mais sa note VU#393783 sur le sujet ne couvre que quelques éditeurs. En effet, d'autres implémentations (ex: mSLP en Java) semblent être elles aussi vulnérables. Enfin, certains standards (comme SMI-S pour le stockage) imposent l'utilisation de SLP. On trouve en ligne des détails sur les tests de conformité SNIA et la liste des sociétés conformes, donc proposant un service SLP potentiellement vulnérable.


En terme d'exploitation, les vecteurs sont nombreux. Plusieurs types de requêtes (dont ServerRequest et AttributeRequest) autorisent les extensions. Et le code vulnérable est joignable en TCP ou UDP unicast, ainsi qu'en UDP broadcast et multicast. Un seul paquet spoofé peut donc suffire à saturer un nombre important d'équipements, par exemple des baies SAN, des imprimantes ou des annuaires. Un serveur VMware sera relativement épargné, seule la CPU réservée à la machine virtuelle Service Console (ou COS) étant occupée.


Un PoC permettant de déclencher le bug dans diverses conditions (SLPick.py) est disponible. Il permet d'exploiter la totalité des vecteurs réseau :

$> ./SLPick.py -h
[=] SLPick : SLP client v0.4 (by Nicolas Gregoire)
Usage : ./SLPick.py [-h] [-m mode] [-p port] [-n number] [-s source_IP] [-t target_IP]
	[-h] Help (this text)
	[-m] Mode : tcp / unicast / broadcast / multicast (default is "unicast")
	[-p] Port : default is "427"
	[-s] Source IP Adress : no default (used only in multicast mode)
	[-t] Target IP Adress : no default (forced in multicast mode)
	[-n] Number of extensions : 0 (no bug) / 1 (default) / 2 (trailing extension)
	[-r] Request type : sr (ServerRequest, default) / ar (AttributeRequest)


Posted by Nicolas Grégoire | Permanent link | File under: vulnérabilités

dimanche 10 juillet 2011, 21:00:02 (UTC+0200)

Audit partiel des composants Open Source intégrés à VMware ESX (SFCB)

Suite à un pari (que j'ai depuis perdu) avec Marc Olanié, je devais trouver une exécution de code à distance dans les produits ESX/vSphere de VMware, simplement en lisant le code de projets Open Source embarqués dans ces produits. Cet article a pour objectif de présenter les vulnérabilités identifiées lors de cette analyse et leur impact (ou pas) sur ESX/vSphere. De plus, la publication des détails techniques permettra d'identifier d'autres produits incluant des versions vulnérables de ces composants ou souffrant de bugs similaires. La première partie, ici présente, couvre le serveur CIMOM, alors que la deuxième (à venir) abordera SLP.


WBEM est une suite de protocoles veillant à fournir aux administrateurs d'informatique distribuée (dite Cloud) des outils centralisés de gestion, suivi et déploiement. SBLIM est l'implémentation de référence, sous Linux, de ces protocoles. La partie "serveur CIMOM" (pour Common Information Model Object Manager) est implémentée sous le nom de SFCB.


sfcbd (version testée : 1.3.6) écoute sur les ports TCP/5988 (HTTP) ou TCP/5989 (HTTPS) et accepte les requêtes POST et M-POST. Ce service étant avant tout un serveur Web "maison", la première chose à tester (en dehors du GET de 4100 caractères ;-) est la gestion de l'en-tête Content-Length, vu le nombre de vulnérabilités liées à ce traitement (Opera, Nagios, CA iGateway, Quicktime, Apache mod_proxy, Novell eDirectory, ...). Et on n'est pas déçu, l'oeil étant immédiatement attiré par un memcpy() (ligne 406) réalisé sur un tampon dont la taille est fournie par l'attaquant (ligne 405) :

401	static int getPayload(CommHndl conn_fd, Buffer * b)
402	{
403	   unsigned int c = b->length - b->ptr;
404	   int rc = 0;
405	   b->content = (char *) malloc(b->content_length + 8);
406	   if (c) memcpy(b->content, (b->data) + b->ptr, c);
407	
408	   if (c > b->content_length) {
409	     mlogf(M_INFO,M_SHOW,"--- HTTP Content-Length is lying; content truncated\n");
410	     c = b->content_length;
411	   }
412	
413	   rc = readData(conn_fd, (b->content) + c, b->content_length - c);
414	   *((b->content) + b->content_length) = 0;
415	   return rc;
416	}

Le critère de taille est bel et bien vérifié, mais *après* la copie, donc trop tard. Et hop, un heap overflow (CVE-2010-1937) ! Le correctif pour cette vulnérabilité est trivial, et consiste simplement à vérifier les données *avant* de les utiliser (cf. le diff coloré). Ce qui donne ce type de code :

401	static int getPayload(CommHndl conn_fd, Buffer * b)
402	{
403	   unsigned int c = b->length - b->ptr;
404	   int rc = 0;
405	
406	   if (c > b->content_length) {
407	     mlogf(M_INFO, M_SHOW,
408	           "--- HTTP Content-Length is lying; rejecting %d %d\n", c, b->content_length);
409	     return -1;
410	   }
411	
412	   b->content = (char *) malloc(b->content_length + 8);
413	   if (c) memcpy(b->content, (b->data) + b->ptr, c);
414	
415	   rc = readData(conn_fd, (b->content) + c, b->content_length - c);
416	   *((b->content) + b->content_length) = 0;
417	   return rc;
418	}

Pour autant, l'instruction malloc(b->content_length + 8) (ligne 412) est toujours vulnérable à un débordement d'entier débouchant sur un débordement de tampon si aucune vérification préalable n'a lieu. La fonction doHttpRequest() réalise en amont diverses vérifications sur la valeur du champ Content-Length, dont deux qui nous intéressent dans le cas présent :

841	         unsigned int maxLen;
842	         if (getControlUNum("httpMaxContentLength", &maxLen) != 0) {
843	           genError(conn_fd, &inBuf, 501, "Server misconfigured (httpMaxContentLength)", NULL);
844	           _SFCB_TRACE(1, ("--- exiting: bad config httpMaxContentLength"));
845	           commClose(conn_fd);
846	           exit(1);
847	         }
848	         if((clen >= UINT_MAX) || ((maxLen) && (clen > maxLen))) {
849	            genError(conn_fd, &inBuf, 413, "Request Entity Too Large", NULL);
850	            _SFCB_TRACE(1, ("--- exiting: content-length too big"));      
851	            commClose(conn_fd);
852	            exit(1);
853	         }
854	         inBuf.content_length = clen;

La variable clen correspond au b->content_length de getPayload(). Nous avons donc trois cas possibles, selon la valeur de l'option de configuration httpMaxContentLength :

  • elle n'est pas définie dans le fichier de configuration => une erreur est levée
  • elle est définie et vaut zéro => la variable clen doit être inférieure à UINT_MAX
  • elle est définie et est différente de zéro => la variable clen doit être inférieure à UINT_MAX et inférieure ou égale à httpMaxContentLength

Ce débordement d'entier est donc exploitable (CVE-2010-2054) si httpMaxContentLength vaut zéro ou est légèrement inférieure à UINT_MAX. Le correctif (cf. diff coloré) se contente d'interdire la valeur zéro :

842	         if ((getControlUNum("httpMaxContentLength", &maxLen) != 0) || (maxLen == 0)) {

La couverture n'est donc pas totale. Un administrateur mal intentionné pourrait par exemple positioner la variable à 4.294.967.290 (soit UINT_MAX - 5), et ainsi introduire un heap overflow exploitable à distance et sans authentification, sans pour autant impacter les aspects fonctionnels :-(


Je me sentais du coup bien parti pour gagner mon pari ! Sauf que les versions testées à l'époque (VMware ESXi 3.5, ESXi 4 et ESX 4) intégraient une version antérieure et modifiée de sfcbd (v1.3.3 sous ESX 4). CVE-2010-1937 y a été corrigé (silencieusement, c'est-à-dire sans informer ni ses clients ni le projet Open Source d'origine) et CVE-2010-2054 n'affecte pas les versions inférieures à 1.3.4.


Carrramba ! Encore raté ! Mais bon, la surface d'attaque d'un ESX côté réseau d'administration est vaste, et SLP fera l'objet de la deuxième partie de cette étude ...


Posted by Nicolas Grégoire | Permanent link | File under: vulnérabilités

webmaster@agarri.fr
Copyright 2010-2021 Agarri