Vous avez franchi la ligne rouge — Perturber le repos d'un hôte
Synthèse
- Ben Barnea, chercheur chez Akamai, a découvert deux vulnérabilités importantes dans un service RPC de Microsoft Windows qui ont été attribuées CVE-2022-37998 et CVE-2022-37973 avec un score de base de 7,7.
- Ces vulnérabilités tirent parti de plusieurs bogues dans l'interface RPC du Local Session Manager.
- Elles entraînent des attaques par déni de service qui empêchent les services de conteneurs et de sessions (tels que Microsoft Defender Application Guard, Sandbox, Docker et Windows Terminal Server) de fonctionner.
- Cette faille existe sur les machines Windows 10, Windows 11 et Windows Server 2022 non corrigées.
- Ces vulnérabilités ont été révélées de manière responsable à Microsoft et corrigées dans le Patch Tuesday d'octobre 2022.
- Nous fournissons une démonstration de ces vulnérabilités dans notre référentiel de recherche.
Introduction
Le groupe Security Intelligence d'Akamai a analysé en détail le protocole MS-RPC au cours de l'année écoulée. Pour un protocole qui remplit autant de fonctions, MS-RPC est largement sous-étudié, et cela peut avoir des répercussions concrètes. L'un de ces effets est que les vulnérabilités d'une interface RPC sont exposées. C'est sur ce point que nous nous concentrons dans ce billet de blog : les vulnérabilités de l'interface RPC du Local Session Manager (LSM).
LSM est un service qui fait partie du sous-système Session Manager. Il est responsable de la gestion des sessions locales liées aux sessions du serveur terminal sur une machine Windows. Il communique avec d'autres composants Windows connexes, tels que Winlogon et Csrss.
LSM est implémenté dans lsm.dll et il contient à la fois une logique client et une logique serveur. LSM expose plusieurs interfaces RPC, dont une interface intéressante liée à la gestion des sessions des conteneurs qui sont exécutés dans une machine virtuelle Hyper-V. Les vulnérabilités se trouvent au sein de cette interface.
Quelle est cette interface ?
La nouvelle interface RPC se voit attribuer l'UUID c938b419-5092-4385-8360-7cdc9625976a. Cette interface expose exactement deux fonctions : ContainerCom_AskForSession et ContainerCom_SessionLoggedOff. L'interface est également enregistrée avec un rappel de sécurité qui renvoie toujours RPC_S_OK, permettant ainsi l'accès à tous les utilisateurs. Le serveur LSM enregistre un point de terminaison Hyper-V socket (hvsocket) uniquement accessible aux conteneurs Hyper-V.
Fig. 1 : Le client établit une connexion RPC sur hvsocket et le serveur établit un point de terminaison hvsocket
Lorsqu'une session est créée au sein du conteneur (par exemple, à cause d'une connexion RDP), le client LSM appelle d'abord RpcGetRequestForWinlogon dans le LSM du conteneur. Cette fonction arbitre la création de la session et, lorsqu'elle s'exécute dans un conteneur, elle demande d'abord l'autorisation de l'hôte. Pour ce faire, elle invoque l'appel RPC ContainerCom_AskForSession à l'hôte en utilisant un hvsocket vers le parent. L'interface RPC limite le nombre de sessions aux conteneurs. Pour ce faire, elle garde la trace des sessions nouvellement créées.
Comment LSM assure-t-il le suivi des sessions ?
La réponse est simple. Il existe un objet global appelé ContainerSessionServer qui contient deux variables permettant de conserver la trace des sessions :
- Le compteur du nombre total de sessions créées. Ce compteur est limité à un, ce qui signifie qu'une seule session est autorisée à un instant T.
- Une carte entre le GUID d'un conteneur et le nombre de sessions de ce conteneur. Cette variable est limitée à deux pour chaque conteneur.
Chaque fois qu'un conteneur demande une session, ContainerSessionServer::AskForSession vérifie d'abord si le compteur total de sessions est inférieur à un. Si c'est le cas, il incrémente le nombre total de sessions ainsi que le compteur de sessions du conteneur dans la carte.
Lorsque ContainerSessionServer::OnSessionLoggedOff est appelé (à la sortie du conteneur ou directement en tant qu'appel RPC), la fonction diminue d'une unité le nombre total de sessions et le nombre de sessions du conteneur.
Vérification des vulnérabilités des fonctions RPC
Bien que cette interface semble simple et facile à mettre en œuvre, nous avons trouvé quatre bogues que nous avons réussi à enchaîner en deux vulnérabilités.
Chaîne n° 1 — DoS via une section critique — CVE-2022-37998
Bogue n° 1 — Impossible de sortir d'une section critique
ContainerSessionServer::AskForSession utilise une section critique pour synchroniser l'accès à l'objet global ContainerSessionServer.
Fig. 2 : Code décompilé de la vulnérabilité. La fonction se termine sans libérer la section critique
Comme indiqué ci-dessus, la section critique est entrée à la ligne 112. Plus tard, aux lignes 114-116, on vérifie si le compteur de session du conteneur est à sa limite (deux). Si c'est le cas, LSM ne garde pas de trace de cette session et quitte immédiatement la fonction (ligne 125). Malheureusement, le code ne quitte pas la section critique qui a été saisie. Par conséquent, les appels ultérieurs à cette interface seront bloqués en attendant que cette section critique soit libérée.
Toutefois, comme nous l'avons précisé, il y a une limite de un pour le compteur de session total. Dès lors, comment pouvons-nous arriver à un point où le compteur de session d'un conteneur est de deux ? Eh bien, logiquement, ce n'est pas possible. Mais c'est alors qu'apparaît le deuxième bogue !
Bogue n° 2 — Suivi incorrect du compteur
Lorsqu'une session du conteneur est déconnectée, un appel RPC est effectué vers ContainerSessionServer::OnSessionLoggedOff dans l'hôte. Cette fonction diminue d'abord le compteur total de sessions en appelant DecreaseTotalSessionCount. Elle le fait indépendamment du fait que le conteneur soit suivi ou non. Si elle découvre que le conteneur n'est pas suivi, elle se termine sans réincrémenter le compteur total de sessions.
Cela peut conduire à un compteur total de sessions ayant la valeur d'un nombre négatif (car il s'agit d'un entier signé). Nous pouvons simplement envoyer de nombreuses requêtes OnSessionLoggedOff avant d'envoyer des requêtes AskForSession et ainsi continuer à décrémenter le compteur de sessions totales jusqu'à un nombre négatif arbitraire.
Enchaîner le premier et le deuxième bogue
Nous pouvons utiliser le bogue n° 2 pour diminuer le compteur de sessions totales plusieurs fois jusqu'à ce qu'il devienne un nombre négatif. Ensuite, nous pouvons exploiter le bogue n° 1 en envoyant deux requêtes à AskForSession. La deuxième fois que cette fonction est appelée, elle vérifie que le compteur de sessions totales est inférieur à 1 et, en raison du deuxième bogue, c'est bien le cas. Ensuite, elle verra que le compteur de sessions du conteneur est égal à 2, et elle reviendra sans sortir de la section critique.
Fig. 3 : Un aperçu du processus d'exploitation
Le DoS dépend du fait que les nouveaux appels RPC entrants sont distribués au même thread que celui qui a créé le blocage de la section critique. Si le runtime RPC distribue le nouvel appel au même thread, le DoS ne se produit pas puisque EnterCriticalSection autorise la propriété imbriquée, c'est-à-dire que le même thread peut appeler EnterCriticalSection deux fois. Si l'appel RPC était envoyé à un autre thread que celui qui détient la section critique, l'attente serait interminable.
Chaîne n° 2 — DoS par fuite de mémoire — CVE-2022-37973
Bogue n° 3 — Fuite de mémoire
ContainerSessionServer::AskForSession assure également le suivi des événements liés aux conteneurs, comme la sortie, la pause et la reprise du conteneur. Pour ce faire, il faut appeler HcsOpenComputeSystem avec le GUID du conteneur, puis enregistrer une fonction de rappel avec HcsRegisterComputeSystemCallback.
La fonction de rappel enregistrée reçoit un objet de contexte. Le contexte est alloué dans ContainerSessionServer::AskForSession. Malheureusement, dans de nombreux cas où une erreur se produit, la fonction se termine sans libérer la mémoire allouée au contexte. Cela entraîne une fuite de mémoire qu'un pirate peut déclencher plusieurs fois. Après un nombre suffisant d'appels, la mémoire du processus LSM s'épuise et le processus s'arrête.
Dans nos tests, l'envoi de requêtes RPC dans une boucle sans fin a donné lieu à une allocation d'environ 3 Mo par seconde. Dans notre cas, après avoir alloué 24 Go de mémoire, le service LSM s'est arrêté. Le temps nécessaire à l'extraction de 24 Go a été d'environ deux heures. Le service n'est pas automatiquement réactivé.
Bogue n° 4 — Accès à distance
Les points de terminaison dans MS-RPC sont multiplexés. Si un serveur enregistre plusieurs interfaces et plusieurs points de terminaison, chaque interface est accessible par chaque point de terminaison. Les points de terminaison et les interfaces ne sont pas liés les uns aux autres.
Cette interface est censée être uniquement accessible aux conteneurs via la hvsocket. Dans notre cas, LSM enregistre un point de terminaison de canal nommé « \pipe\LSM_API_service », qui est accessible à distance. En raison du multiplexage des points de terminaison, un pirate peut se connecter à distance au point de terminaison du canal nommé et envoyer une requête à l'interface du conteneur. La solution est simple : la fonction de rappel de sécurité devrait vérifier quel point de terminaison le client a utilisé, et si ce n'est pas hvsocket, refuser l'accès.
Enchaîner le troisième et le quatrième bogue
La fonctionnalité de suivi des conteneurs est basée sur la propriété de l'identifiant du client. Cela signifie que pour un hvsocket, l'identifiant du client sera le GUID du conteneur. Pour un canal nommé, ce sera le nom de la machine du client.
Pour déclencher le premier exploit, le client doit avoir un identifiant client qui est un GUID réel d'un conteneur en cours d'exécution. En tant que tel, un client distant ne serait pas en mesure de déclencher ces bogues à moins qu'il ne réussisse à vérifier le GUID d'un conteneur en cours d'exécution et à changer son nom de machine, un scénario qui paraît improbable.
Malheureusement, le troisième bogue (fuite de mémoire) alloue les objets avant toute vérification du conteneur demandé. Cela signifie qu'un pirate (utilisant le bogue n° 4) peut déclencher à distance la fuite de mémoire. En l'appelant plusieurs fois, un pirate peut provoquer l'épuisement de la mémoire et faire planter le processus.
Impact
Bien que ces vulnérabilités soient classées comme des vulnérabilités de déni de service (DoS), elles ont un impact sur la sécurité, car elles permettent à un pirate de contourner les fonctions de sécurité.
Avec la première faille (section critique), il y a un DoS sur la nouvelle interface spécifique. Ce problème empêcherait la création de nouvelles instances de sandbox.
Avec la deuxième faille, qui peut être déclenchée à distance et à partir d'un conteneur, l'ensemble du processus s'arrête. Dans ce scénario, toutes les dépendances du LSM ne fonctionneraient pas. Les fonctionnalités de sécurité telles que Microsoft Defender Application Guard et Sandbox ne seraient pas non plus opérationnelles. Il en serait de même pour RDP et Docker.
Fig. 4 : Erreur MDAG affichée dans Microsoft Edge
Synthèse
Ces vulnérabilités sont un excellent exemple de la façon dont un élément qui semble simple ou insignifiant en apparence peut avoir des répercussions très négatives. Cette interface peut sembler mineure : Elle nous a présenté quelques bogues faciles à déclencher et dont l'impact est intéressant.
Le fait est que des acteurs malveillants utilisent ces chaînes d'attaque au quotidien. Plus une chose semble insignifiante, plus elle est facilement ignorée, et cela constitue une faille de choix à exploiter.
Parallèlement au travail continu que nous effectuons dans le domaine des RPC, nous encourageons les autres chercheurs à détecter des bogues similaires dans d'autres interfaces RPC.
Si vous vous intéressez à des sujets de recherche RPC tels que celui-ci, consultez notre boîte à outils RPC pour plus d'articles et d'outils liés à ce sujet. Vous pouvez également nous suivre sur Twitter pour obtenir des mises à jour en temps réel sur ce sujet et les autres recherches que nous menons ici, à Akamai.