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)
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 ...