La dure réalité du cache : Contournement de la sécurité de l'interface RPC par usurpation de cache
Synthèse
Les chercheurs Akamai ont découvert deux vulnérabilités importantes dans les services Microsoft Windows RPC qui ont été attribuées CVE-2022-38034 et CVE-2022-38045 avec des scores de base de 4,3 et 8,8, respectivement.
Ces vulnérabilités tirent parti d'un défaut de conception qui permet de contourner les rappels de sécurité MS-RPC par le biais de la mise en cache.
Il s'avère que cette vulnérabilité existe dans les machines Windows 10 et Windows 11 non protégées par des correctifs.
Ces vulnérabilités ont été révélées de manière responsable à Microsoft et corrigées dans le Patch Tuesday d'octobre.
Le processus de découverte de vulnérabilité est facilité par un outil d'automatisation et une méthodologie développés par les chercheurs d'Akamai.
Nous fournissons une preuve de concept des vulnérabilités et des outils utilisés dans le cadre de nos recherches dans le référentiel de la boîte à outils RPC.
Introduction
MS-RPC est l'une des pierres angulaires du système d'exploitation Windows. Paru dans les années 1990, il s'est depuis enraciné dans la plupart des éléments du système. Le gestionnaire de service ? RPC. Lsass ? RPC. COM ? RPC. Même certaines opérations de domaine effectuées auprès du contrôleur de domaine utilisent RPC. Compte tenu de l'omniprésence de MS-RPC, on pourrait s'attendre à ce qu'il ait fait l'objet d'un examen minutieux, d'une documentation détaillée et de recherches approfondies.
Eh bien, pas vraiment. Bien que la documentation de Microsoft sur l'utilisation de RPC soit relativement correcte, peu de choses ont été écrites sur le sujet, et encore moins par des chercheurs qui se sont penchés sur RPC, et plus précisément sur sa sécurité. Cela peut probablement être attribué au fait que RPC (pas seulement MS-RPC, bien que Microsoft ait certainement ajouté au mélange) est très complexe, ce qui complique les recherches et la compréhension.
Mais comme nous sommes toujours prêts à relever le défi, nous avons décidé de plonger la tête la première dans les profondeurs de MS-RPC. Non seulement parce qu'il s'agit d'un sujet de recherche intéressant, mais aussi en raison de ses implications en matière de sécurité : aujourd'hui encore, les techniques d'attaque courantes reposent sur RPC (T1021.003 se produit via MS-COM, T1053.005 est MS-TSCH, T1543.003 est MS-SCMR, pour n'en nommer que quelques-uns). Des mécanismes de sécurité sont intégrés à MS-RPC, mais que se passe-t-il si des vulnérabilités permettent de les contourner ou d'en abuser, ou si un service RPC exposé peut être utilisé de manière abusive pour avoir un impact sur les machines d'une manière non souhaitée ?
En fait, nous avons réussi à trouver un moyen de contourner un mécanisme de sécurité par le biais de la mise en cache. Grâce à cette solution, nous avons trouvé quelques services dont nous pouvions abuser pour escalader les privilèges sur des serveurs distants, sans beaucoup de conditions requises (que nous détaillerons plus tard dans ce billet). À l'heure actuelle, nous pouvons partager des informations sur deux exemples concrets d'exploitation potentielle, WksSvc et SrvSvc. Nous publierons des mises à jour sur les autres vulnérabilités que nous avons trouvées une fois leur processus de divulgation terminé.
Dans ce billet de blog, nous nous concentrerons sur le mécanisme de rappel de sécurité des serveurs RPC, sur la façon dont il peut être contourné par la mise en cache et sur la façon dont nous avons automatisé notre recherche pour signaler les services Windows comme potentiellement vulnérables. Nos outils d'automatisation, ainsi que leurs résultats bruts, se trouvent également dans notre boîte à outils RPC, qui est partagée dans notre référentiel GitHub. Notre référentiel contient également des liens vers d'autres références utiles et des travaux réalisés par d'autres chercheurs sur lesquels nous nous sommes appuyés.
Rappels de sécurité
Avant d'aborder les vulnérabilités elles-mêmes, il est important de faire la lumière sur l'un des mécanismes de sécurité les plus fondamentaux mis en œuvre par MS-RPC : les rappels de sécurité. Les rappels de sécurité permettent aux développeurs de serveur RPC de restreindre l'accès à une interface RPC. Ils peuvent ainsi appliquer leur propre logique pour autoriser l'accès à des utilisateurs spécifiques, renforcer l'authentification ou les types de transport, ou empêcher l'accès à des opnums spécifiques (les fonctions exposées par le serveur sont représentées par des opnums, c'est-à-dire des numéros d'opération).
Cette fonction de rappel est lancée par le runtime RPC chaque fois que le client invoque une fonction exposée sur le serveur.
Dans notre recherche, nous nous sommes concentrés sur l'interaction client-serveur à distance. Nous le mentionnons, car les implémentations du code d'exécution RPC côté serveur diffèrent entre un point de terminaison ALPC et un point de terminaison distant tel qu'un canal nommé.
Mise en cache
Le runtime RPC met en œuvre la mise en cache du résultat du rappel de sécurité pour améliorer les performances et l'utilisation. Cela signifie essentiellement que le runtime tentera d'utiliser une entrée en cache avant d'appeler le rappel de sécurité à chaque fois. Examinons la mise en œuvre.
Avant que RPC_INTERFACE::DoSyncSecurityCallback n'invoque le rappel de sécurité, il vérifie d'abord si une entrée de cache existe. Il le fait en appelant OSF_SCALL::FindOrCreateCacheEntry.
OSF_SCALL::FindOrCreateCacheEntry effectue les opérations suivantes :
Il extrait le contexte de sécurité du client du SCALL (un objet qui représente un appel client).
Il extrait le dictionnaire de mise en cache du contexte de sécurité du client.
Il utilise le pointeur d'interface comme clé du dictionnaire. La valeur est l'entrée de cache.
S'il n'existe aucune entrée de cache, il en crée une.
Une entrée de cache comporte trois champs importants : le nombre de procédures dans l'interface, un bitmap et la génération de l'interface.
Pendant la durée de vie d'un serveur RPC, l'interface peut être modifiée, par exemple si le serveur appelle RpcServerRegisterIf3 sur une interface existante. Cette dernière appelle à son tour RPC_INTERFACE::UpdateRpcInterfaceInformation, qui met à jour l'interface et augmente la génération de l'interface. De cette façon, la mise en cache sait qu'elle doit être « réinitialisée », car les entrées de cache peuvent être issues de l'ancienne interface.
Le mécanisme de mise en cache peut fonctionner selon deux modes : sur la base d'une interface (qui est le comportement par défaut) et sur la base d'un appel.
Mise en cache basée sur l'interface
Dans ce mode, la mise en cache fonctionne sur la base d'une interface. Cela signifie que du point de vue de la mise en cache, il n'y a pas de différence entre deux appels vers deux fonctions différentes tant qu'ils sont sur la même interface.
Pour savoir si l'entrée de cache peut être utilisée au lieu d'appeler le rappel de sécurité, le runtime RPC compare la génération d'interface enregistrée dans l'entrée de cache avec la génération d'interface réelle. Comme l'initialisation de l'entrée de cache remet à zéro la génération de l'interface, la première fois que la comparaison est effectuée, les générations d'interface sont différentes et, par conséquent, le rappel de sécurité est appelé. Si le rappel a été renvoyé avec succès, le runtime RPC met à jour la génération de l'interface de l'entrée de cache (et par conséquent, elle est « marquée » comme une entrée de cache réussie, une entrée qui permet d'accéder à l'interface sans appeler à nouveau le rappel de sécurité). La prochaine fois que le client appellera une fonction sur la même interface, l'entrée de cache sera utilisée.
Mise en cache basée sur les appels
Ce mode est utilisé lorsque l'interface RPC est enregistrée avec l'indicateur RPC_IF_SEC_CACHE_PER_PROC. Dans ce mode, la mise en cache est basée sur un bitmap qui suit les procédures auxquelles le rappel de sécurité a autorisé l'accès. Par conséquent, si le client a appelé la fonction Foo et que le rappel de sécurité a été renvoyé avec succès, nous aurons une entrée de cache pour Foo. Si le client appelle Bar, le rappel de sécurité est appelé à nouveau.
Exigences de mise en cache
De quoi avons-nous besoin pour que la mise en cache fonctionne ? Tout d'abord, nous devons clarifier une certaine terminologie. MS-RPC représente une connexion logique entre un client et un serveur utilisant un handle de liaison. Le client et le serveur peuvent manipuler les données de liaison à l'aide des fonctions désignées.
Une liaison peut être authentifiée. Cela se produit lorsque le serveur enregistre les informations d'authentification (en appelant RpcServerRegisterAuthInfo), puis le client définit les informations d'authentification sur la liaison. Cela permet au serveur de récupérer des informations sur l'identité du client. Le résultat de ce processus d'authentification est un objet de contexte de sécurité créé pour le client.
L'ensemble du mécanisme de mise en cache est basé sur ce contexte de sécurité. Cela signifie que si la liaison n'est pas authentifiée, aucun contexte de sécurité n'est créé pour le client et la mise en cache n'est donc pas activée. Pour que la mise en cache fonctionne, le serveur et le client doivent tous deux enregistrer et définir les informations d'authentification.
Mais que se passe-t-il si le serveur n'a pas enregistré les informations d'authentification ? La mise en cache peut-elle toujours être activée ? Introduction : multiplexage.
Multiplexage
Jusqu'à Windows 10, version 1703, un service pouvait partager le même processus svchost avec d'autres services. Ce comportement affecte la sécurité MS-RPC car certains des objets d'exécution RPC sont partagés entre toutes les interfaces. Par exemple, lors de l'enregistrement d'un point de terminaison (tel que le port TCP 7777), ce dernier peut être utilisé pour accéder à toutes les interfaces qui s'exécutent sous le même processus. Par conséquent, d'autres services auxquels on s'attend à accéder localement sont désormais également accessibles à distance. Cette procédure est également décrite sur cette page par Microsoft.
Bien que le fait que les points de terminaison soient multiplexés soit déjà quelque peu connu et documenté, nous aimerions présenter un autre comportement similaire : le multiplexage SSPI. Dans le cadre de l'enregistrement des informations d'authentification, le serveur doit spécifier le service d'authentification à utiliser. Le service d'authentification est un fournisseur SSP (Security Support Provider), qui est un paquet qui traite les informations d'authentification reçues du client. Dans la plupart des cas, il s'agit du SSP NTLM, du SSP Kerberos ou du SSP Microsoft Negotiate, qui choisit la meilleure option disponible entre Kerberos et NTLM.
Le runtime RPC enregistre les informations d'authentification de manière globale. Cela signifie que si deux serveurs RPC partagent le même processus et que l'un d'eux enregistre les informations d'authentification, l'autre serveur aura également des informations d'authentification. Un client peut désormais authentifier la liaison lors de l'accès à chacun des serveurs. Du point de vue de la sécurité, les serveurs qui n'ont pas enregistré les informations d'authentification et qui, par conséquent, ne s'attendaient pas à ce que les clients authentifient la liaison ou à ce que la mise en cache ait lieu, peuvent se voir imposer ces mesures.
CVE-2022-38045 — srvsvc
Armés de nos nouvelles connaissances sur les rappels de sécurité RPC et la mise en cache, nous avons entrepris de voir si nous pouvions exploiter ce mécanisme dans des conditions réelles. Nous sommes revenus à srvsvc, dans lequel nous avons déjà détecté une vulnérabilité isolée auparavant.
Srvsvc expose l'interface MS-SRVS. Le service Server (également appelé LanmanServer) est un service Windows qui s'occupe de la gestion des partages SMB. Les partages sont des ressources (fichiers, imprimantes et arborescences de répertoires) accessibles sur le réseau par le biais d'un serveur CIFS (Common Internet File System). Sur le fond, les partages réseau permettent d'utiliser d'autres terminaux présents sur le réseau pour effectuer diverses tâches quotidiennes.
Lorsque nous avons examiné le rappel de sécurité de Srvsvc, nous avons remarqué que la fonction pouvait présenter une autre vulnérabilité, différente de celle que nous avions déjà trouvée. Examinons la logique du rappel de sécurité :
Comme indiqué ci-dessus, le rappel de sécurité de srvsvc obéit à la logique suivante :
Si un client distant tente d'accéder à une fonction comprise entre 64 et 73 (inclus) : refuser l'accès
Si un client distant, autre qu'un compte cluster, tente d'accéder à une fonction comprise entre 58 et 63 (inclus) : refuser l'accès
Par conséquent, dans l'absolu, les clients distants ne peuvent pas accéder à ces fonctions particulières de l'interface. Cette vérification de plage suggère que les fonctions restreintes sont sensibles et doivent être appelées uniquement par les processus (locaux) attendus.
Bien que cette vérification tente d'empêcher l'accès à distance à ces fonctions, un attaquant distant peut la contourner en abusant de la mise en cache. Tout d'abord, un attaquant distant appelle une fonction qui n'est pas dans cette plage, une fonction disponible à distance. Comme la fonction de rappel de sécurité renvoie RPC_S_OK, le runtime RPC va mettre en cache le résultat comme une réussite. Comme l'interface n'est pas enregistrée avec l'indicateur RPC_IF_SEC_CACHE_PER_PROC, la mise en cache sera basée sur une interface. Par conséquent, la prochaine fois que l'attaquant appellera n'importe quelle fonction sur la même interface, l'entrée de cache sera utilisée et l'accès sera accordé. Cela signifie que l'attaquant peut désormais appeler les fonctions locales auxquelles il ne devrait pas avoir accès et que le rappel de sécurité ne sera pas du tout appelé.
Srvsvc n'enregistre pas les informations d'authentification ; dans des circonstances normales, les clients ne peuvent donc pas authentifier la liaison. Par conséquent la mise en cache n'est pas activée. Il s'avère que Srvsvc partage le même processus svchost avec d'autres services lorsque le serveur dispose de moins de 3,5 Go de RAM. Les services « AD Harvest Sites and Subnets Service » et « Remote Desktop Configuration service » enregistrent les informations d'authentification. Par conséquent, srvsvc est désormais vulnérable aux attaques de cache.
Dans ce scénario spécifique, un attaquant peut accéder à des fonctions restreintes avec les opnums 58 à 74. Un attaquant peut, entre autres, utiliser ces fonctions pour forcer l'authentification de la machine distante.
Une chasse au trésor
Après avoir compris que l'utilisation abusive du mécanisme de mise en cache de la fonction de rappel de sécurité pouvait entraîner des vulnérabilités réelles, nous avons décidé d'essayer de trouver d'autres interfaces qui pourraient être vulnérables à une attaque par mise en cache. Mais trouver toutes les interfaces manuellement serait une tâche longue et ardue, nous avons donc voulu trouver un moyen de l'automatiser.
Nous avons deux approches possibles pour rechercher les interfaces RPC : via les processus en cours d'exécution ou via le système de fichiers.
Avec les processus en cours d'exécution, nous pouvons rechercher les serveurs RPC déjà chargés en mémoire, soit sur un serveur distant en interrogeant le mappeur de points de terminaison distants (avec la commande rpcmap ou rpcdumpd'Impacket, par exemple) ou localement (en utilisant des outils comme RpcView ou RpcEnum). Il y a cependant un problème avec cette approche : Nous manquons toutes les interfaces qui ne sont pas actuellement chargées, et nous ne sommes pas en mesure d'examiner les interfaces client, puisqu'elles ne sont pas enregistrées.
Une autre solution consiste à parcourir le système de fichiers Windows et à rechercher les interfaces RPC qui y sont compilées. Nous analysons les informations d'enregistrement de chaque interface en examinant les arguments transmis à la commande RpcServerRegisterIf. Cette approche est similaire à celle de RpcEnum, mais nous analysons le système de fichiers au lieu de la mémoire.
Dans notre recherche, nous avons choisi la méthode du système de fichiers afin d'inclure des interfaces qui n'étaient pas nécessairement chargées en mémoire. Nous avons écrit plusieurs scripts et outils pour automatiser le processus, qui sont disponibles dans notre référentiel de la boîte à outils RPC.
Pour trouver les interfaces dont la mise en cache est activée, nous n'avons pas vraiment besoin d'analyser l'interface RPC elle-même. Toutes les informations requises peuvent être extraites de l'appel d'enregistrement du serveur RPC. La fonction d'enregistrement accepte la structure de l'interface RPC, les indicateurs d'enregistrement et le pointeur de rappel de sécurité. Cependant, l'analyse de la structure de l'interface RPC peut fournir des informations utiles, comme les fonctions exposées par l'interface ou si elle est utilisée par un serveur ou un client RPC. Bien que nous soyons principalement intéressés par les serveurs RPC (où une vulnérabilité peut exister), les clients RPC fournissent un bon aperçu de l'appel du serveur, que nous pouvons référencer pour l'exploitation.
La structure de l'interface du serveur RPC est documentée.Nous n'avons donc pas à deviner ses champs. De plus, le champ de taille et la syntaxe de transfert sont constants (il existe en fait deux syntaxes de transfert possibles, DCE NDR et NDR64, mais nous ne sommes tombés que sur DCE NDR).
Trouver toutes les structures d'interface RPC en recherchant ces deux constantes (en utilisant Yara ou des expressions régulières) est un jeu d'enfant. Une fois trouvées, nous pouvons utiliser le champ d'informations de l'interpréteur pour voir quelles fonctionnalités le serveur met en œuvre.
Mais il nous manque encore des informations sur le rappel de sécurité de l'interface (s'il existe) et sur son éventuelle mise en cache. Pour cela, nous devons nous tourner vers nos fidèles amis, les désassembleurs. Tout désassembleur qui se respecte dispose d'une fonctionnalité xref ; il est donc aisé de trouver tous les appels de fonction d'enregistrement d'interface dans un serveur RPC. À partir de là, il nous suffit d'analyser les arguments de l'appel de fonction pour extraire l'adresse de la structure de l'interface (afin de pouvoir la croiser avec les données du serveur RPC que nous avons extraites), l'adresse du rappel de sécurité (si elle existe) et les indicateurs de l'interface RPC.
Nous avons publié nos scripts de scraping, qui font exactement cela ; ils sont disponibles dans notre boîte à outils RPC, de même que leur sortie à partir de Windows Server 2012 et Server 2022.
Les CVE ou rien ne s'est produit
Toutes ces méthodologies et théories sont belles, mais donnent-elles vraiment des résultats ?
La réponse est oui. Il existe plus de 120 interfaces avec un rappel de sécurité et une mise en cache, dont beaucoup sont non documentées. En soi, il n'y a pas lieu de paniquer, car la plupart du temps, le rappel de sécurité n'est guère affecté par la mise en cache. En général, les vérifications effectuées par le rappel de sécurité portent sur des valeurs qui ne peuvent être mises en cache, comme la séquence du protocole de transport (par exemple, TCP) ou le niveau d'authentification. Toute modification à ce niveau nécessite de toute façon un nouveau contexte de sécurité, puisqu'une nouvelle connexion doit être établie, ce qui réinitialise le cache et annule tout contournement possible de la mise en cache.
Nous avons trouvé quelques vulnérabilités grâce à cette approche de recherche. Nous ne pouvons aborder qu'une seule d'entre elles pour le moment, car les autres sont encore en cours de divulgation.
WksSvc
Score CVSS CVE-2022-38034 : 4,3
WksSvc expose l'interface MS-WKST. Le service est responsable de la gestion des appartenances à un domaine, des noms d'ordinateur et des connexions aux redirecteurs réseau SMB, comme les serveurs d'impression SMB. En examinant le rappel de sécurité de l'interface, nous pouvons constater que quelques fonctions sont traitées différemment du reste.
Les fonctions dont l'opnum est compris entre 8 et 11 sont également vérifiées pour être appelées par un client local, ce qui signifie que leur appel à distance n'est pas autorisé. Mais puisque nous disposons d'un cache, que se passe-t-il si nous appelons d'abord une autre fonction, qui est autorisée à distance, puis l'une des fonctions restreintes ?
Vous l'avez deviné : Nous serions en mesure d'appeler les fonctions restreintes localement à distance grâce à la mise en cache du résultat du premier appel. La question qui se pose désormais est la suivante : Ces fonctions sont-elles suffisamment importantes pour justifier leur restriction aux seuls clients locaux ?
Les fonctions exposées sont NetrUseAdd, NetrUseGetInfo, NetrUseDelet NetrUseEnum. Si elles vous semblent familières, c'est parce qu'elles sont accessibles par netApi32.dll ( NetUseAdd, par exemple).
C'est une bonne chose, car cela nous donne un indice sur ce que nous pouvons faire avec cette attaque. Nous pouvons connecter le serveur distant à un dossier partagé du réseau de notre choix, et même le mapper à une lettre de lecteur logique de notre choix, de la même manière que net use. (Coïncidence ? Probablement pas.)
Cela nous donne deux scénarios d'attaque :
1. Nous pouvons exiger une authentification sur notre dossier partagé ; nous pouvons ensuite soit la relayer vers un autre serveur pour une attaque par relais NTLM, soit stocker les jetons et craquer le mot de passe hors ligne.
2. Nous pouvons également usurper un serveur de fichiers existant (ou prétendre en être un nouveau) avec des fichiers intéressants ou utiles. Puisque ces fichiers sont sous notre contrôle, nous pouvons les utiliser comme bon nous semble, en espérant que cela nous permettra d'infecter l'utilisateur cible.
Ces deux scénarios, ainsi que la possibilité d'appeler à distance des fonctions restreintes localement, ont suffi à Microsoft pour classer cette vulnérabilité dans la catégorie EOP, avec un score de 4,3.
Mais l'histoire ne s'arrête pas là : il nous reste encore quelques réserves à lever.
Contexte de sécurité
Le serveur RPC sous WksSvc n'effectue pas d'enregistrement d'authentification par lui-même. Si le service s'exécute seul, aucune authentification côté client ne sera possible (cela entraîne l'erreur RPC_S_UNKNOWN_AUTHN_SERVICE). Par conséquent, nous devons faire tourner ce service avec d'autres afin de tirer parti du multiplexage SSPI. Cela limite nos versions Windows affectées à celles antérieures à Windows 10, version 1703, ou les versions plus récentes qui s'exécutent avec moins de 3,5 Go de RAM.
Sessions de connexion
Un autre problème, qui est intégré dans le fonctionnement des dossiers mappés en réseau, est qu'ils sont limités à la session de connexion de l'utilisateur qui les crée. Étant donné que nous devons nous connecter en premier lieu pour obtenir la liaison de sécurité et la mise en cache, cela signifie que nous créerons toujours une session de connexion différente de la session (interactive) existante sur l'ordinateur cible. À toutes fins utiles, cela signifie que notre vulnérabilité n'a aucun effet. Le mappage réseau que nous créons se trouve sous notre session de connexion éphémère, et non sous celle créée par un utilisateur normal lorsqu'il se connecte à la machine ; il ne sera donc pas visible.
Pour surmonter cela, nous avons dû creuser un peu plus profondément dans le code de NetrUseAdd. Il s'avère qu'il existe des indicateurs que nous pouvons transmettre à NetrUseAdd pour qu'il crée le mappage dans l'espace de noms global, ce qui affecte tous les utilisateurs. Ces indicateurs se trouvent même dans le fichier d'en-tête disponible LMUse.h :
Muni de ces indicateurs, notre code réussit désormais à créer un mappage global, qui affecte la session interactive, mettant fin à notre tentative d'exploitation.
Synthèse
MS-RPC est un protocole vaste et complexe. Il sert également certaines des fonctionnalités de base de Windows. Bien qu'il dispose de fonctions de sécurité que les développeurs peuvent utiliser pour sécuriser leurs serveurs RPC, il constitue un sujet intéressant pour les chercheurs en sécurité, précisément parce qu'il contient une vulnérabilité qui peut avoir un impact sur la sécurité.
Malgré cela, peu de recherches publiques ont été menées sur le sujet. Dans ce billet de blog, nous avons abordé un mécanisme de sécurité important dans MS-RPC, les rappels de sécurité, et avons trouvé un contournement sous la forme de la mise en cache des résultats des rappels. Nous avons également détaillé notre méthodologie de recherche pour détecter des serveurs RPC vulnérables et démontré certains de nos résultats avec des rapports de vulnérabilité.
Nous espérons que cet article, ainsi que le référentiel de la boîte à outils RPCpourront aider d'autres personnes dans leurs recherches sur les serveurs RPC et les mécanismes de sécurité.