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