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