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

webmaster@agarri.fr
Copyright 2010-2021 Agarri