Hackvens
<-- home

articles / Coerced Potato

October 9, 2023

CoercedPotato - Une patate de plus ! đŸ„”

Table des matiĂšres

1. Introduction

2. Une histoire de privilĂšges

3. Les Access Token Windows

4. Parlons bien, parlons « Named Pipe »

5. Un peu de Coercition d’authentification

6. Un peu de code maintenant (C++ on fire) !

7. Remerciements

Introduction

Depuis 2016, de nombreux exploits nommĂ©s « Potatoes » ont Ă©tĂ© dĂ©couverts et sont utilisĂ©s dans le but d’élever ses privilĂšges dans un systĂšme d’exploitation Windows. Le principe est toujours le mĂȘme : passer d’un compte ayant les privilĂšges adĂ©quats, souvent un compte de service, Ă  NT AUTHORITY/SYSTEM (le compte le plus privilĂ©giĂ© sous Windows).

L’objectif de cet article n’est pas de passer en revue la collection « Potatoes » disponible Ă  ce jour. Pour cela, l’excellent article de @Blackwasp est disponible Ă  l’URL suivant : https://hideandsec.sh/books/windows-sNL/page/in-the-potato-family-i-want-them-all

En revanche, la combinaison de plusieurs concepts connus a permis la crĂ©ation d’un nouvel outil : « CoercedPotato ». Cet outil permet notamment d’élever ses privilĂšges sur les versions les plus rĂ©centes de “Windows 10” et “Windows Server 2022”, Ă  date de l’article.

Une image contenant texte, capture d’écran, Police Description gĂ©nĂ©rĂ©e automatiquement

Notez que nous parlons de « nouvel outil » et non pas « nouvelle technique », dans la mesure oĂč celui-ci concatĂšne les connaissances actuelles concernant les impersonate token et les mĂ©thodes permettant de forcer des authentifications via des fonctions RPC vulnĂ©rables. Ces deux concepts seront expliquĂ©s au fur et Ă  mesure de l’article.

Mais avant de commencer, il va falloir passer en revue plusieurs fondamentaux.

Une histoire de privilĂšges

« If you have SeAssignPrimaryToken or SeImpersonatePrivilege, you are SYSTEM ». C’est une citation issue d’un tweet (un X ?) de @decoder_it qui n’est en somme pas trĂšs loin de la rĂ©alitĂ©.

Lors d’un test d’intrusion, et plus particuliĂšrement en test d’intrusion interne, nous parvenons frĂ©quemment Ă  exĂ©cuter du code Ă  distance. Dans le cas d’un systĂšme Windows, une fois une invite de commande obtenue sur la machine ciblĂ©e, nous nous retrouvons parfois dans la situation suivante : nous exĂ©cutons des commandes dans le contexte de sĂ©curitĂ© de l’utilisateur NT AUTHORITY\LOCAL SERVICE.

Une image contenant texte, capture d’écran, Police, ligne Description gĂ©nĂ©rĂ©e automatiquement

Ce compte dispose de privilĂšges restreints sur le systĂšme. L’objectif est donc d’élever nos privilĂšges et d’obtenir une invite de commandes dans le contexte de l’utilisateur NT AUTHORITY\SYSTEM, afin de prendre le contrĂŽle complet du systĂšme. Cela peut ensuite permettre de tenter de rebondir sur d’autres machines du rĂ©seau, en rĂ©cupĂ©rant des identifiants en mĂ©moires vives, en interagissant avec les access tokens de Windows, en rĂ©cupĂ©rant la base des utilisateurs locaux, etc. Mais
 on s’égare ! 😊

Pour revenir au sujet initial, lorsque nous listons les privilùges de l’utilisateur NT AUTHORITY\LOCAL SERVICE, celui-ci dispose normalement du privilùge SeImpersonatePrivilege :

Une image contenant texte, capture d’écran, Police Description gĂ©nĂ©rĂ©e automatiquement

C’est ce privilĂšge qui nous intĂ©resse tout particuliĂšrement pour la suite de l’article !

Si nous suivons la documentation officielle de Microsoft, ce privilĂšge permet « l’emprunt d’identitĂ© d’un client aprĂšs l’authentification et la crĂ©ation de droits d’utilisateur d’objets globaux. »

https://learn.microsoft.com/fr-fr/troubleshoot/windows-server/windows-security/seimpersonateprivilege-secreateglobalprivilege

ConcrĂštement, dans un environnement Windows, lorsqu’un utilisateur possĂšde le privilĂšge SeImpersonatePrivilege, il a la possibilitĂ© de dĂ©marrer des processus (c’est-Ă -dire des programmes, par exemple cmd.exe) au nom d’un autre utilisateur. Cela se fait en appelant la fonction CreateProcessWithTokenW() dans le contexte de sĂ©curitĂ© de l’utilisateur.

https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createprocesswithtokenw

A noter qu’il existe un privilĂšge trĂšs similaire Ă  SeImpersonatePrivilege : SeAssignPrimaryToken. Il permet Ă©galement le dĂ©marrage d’un processus au nom d’un autre utilisateur avec la fonction CreateProcessAsUser(), mais nous ne rentrerons pas dans les dĂ©tails dans cet article.

https://learn.microsoft.com/fr-fr/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasuserw

Toutes les techniques dites « Potatoes » reposent sur ces privilĂšges (Ă  l’exception de RemotePotato) pour obtenir des droits NT AUTHORITY\SYSTEM (nous appellerons ça les droits SYSTEM pour le reste de l’article) sur une machine Windows afin de la compromettre. Vous l’aurez compris, SeAssignPrimaryToken et SeImpersonatePrivilege sont des privilĂšges trĂšs prĂ©cieux pour un attaquant et offrent (quasi) toujours la possibilitĂ© d’élever ses privilĂšges.

L’objectif de l’article est donc de montrer une nouvelle technique exploitant ces privilùges. Il est maintenant temps de rentrer dans le vif du sujet ! 

Les Access Token Windows

En parcourant la documentation de Microsoft, il est possible de retrouver la définition des fonctions énoncées plus tÎt : CreateProcessWithTokenW et CreateProcessAsUserW. La structure de ces fonctions est la suivante :

Une image contenant texte, capture d’écran, nombre, Police Description gĂ©nĂ©rĂ©e automatiquementUne image contenant texte, capture d’écran, Police, nombre Description gĂ©nĂ©rĂ©e automatiquement

Il est intĂ©ressant de noter que ces deux fonctions nĂ©cessitent un access token en argument pour ĂȘtre utilisĂ©es, et plus spĂ©cifiquement un primary token. Mais qu’est-ce que c’est cette histoire de token ?

Pour reprendre sa dĂ©finition telle que dĂ©crite par Microsoft, les access token sont « des objets qui dĂ©crivent le contexte de sĂ©curitĂ© d’un processus ou d’un thread. ».

ConcrĂštement, l’access token, ou jeton d’accĂšs, est obtenu aprĂšs une authentification rĂ©ussie et contient un ensemble d’informations essentielles pour Windows, tel que l’identitĂ© de l’utilisateur, son groupe, sa liste de contrĂŽle d’accĂšs (ACL), ses privilĂšges et surtout, le type de token. Il pourrait par exemple ĂȘtre comparĂ© Ă  un jeton JWT utilisĂ© par une application web.

Par exemple, si je dĂ©marre le processus cmd.exe avec un access token appartenant Ă  l’utilisateur vagrant, cmd.exe aura les privilĂšges du compte vagrant.

https://learn.microsoft.com/fr-fr/windows/win32/secauthz/access-tokens

Il existe deux types d’access token : les primary token et les impersonation token. Pour comprendre la diffĂ©rence entre ces deux types de jetons, il est nĂ©cessaire de connaĂźtre la diffĂ©rence entre un thread et un processus dans un systĂšme Windows.

Pour faire simple, un processus est un espace mémoire virtuel exécutant du code sur le systÚme. Un thread correspond à du code exécuté depuis un processus. Il est donc temporaire et est détruit une fois terminé.

Pour imager, lorsque l’application Word est utilisĂ©e, le processus WINWORD.exe est lancĂ© sur la machine. Ce processus est dĂ©marrĂ© par l’utilisateur avec son primary token. L’application va ensuite utiliser des threads, par exemple pour gĂ©rer des tĂąches en arriĂšre-plan (affichage de l’interface graphique, traitement des entrĂ©es utilisateur, etc.). Cela permet une expĂ©rience fluide lors de l’utilisation de Word. Ces threads seront exĂ©cutĂ©s Ă  l’aide d’un impersonation token.

https://learn.microsoft.com/fr-fr/windows/win32/com/processes--threads--and-apartments

Maintenant que les bases sont acquises, revenons Ă  nos moutons.

Comme expliquĂ© prĂ©cĂ©demment, pour pouvoir dĂ©marrer un processus dans le contexte d’un utilisateur, il nous faut deux choses : le privilĂšge adĂ©quat (SeImpersonatePrivilege ou SeAssignPrimaryToken) et un primary token. Bonne nouvelle pour nous, pour ce dernier prĂ©requis, les deux types de jetons sont interchangeables grĂące Ă  la fonction DuplicateTokenEx.

https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-duplicatetokenex

Ainsi, l’obtention d’un impersonation token d’un utilisateur (au hasard, le compte SYSTEM) permet, grĂące Ă  DuplicateTokenEx, d’obtenir un primary token et ainsi de crĂ©er un processus dans son contexte de sĂ©curitĂ© (et donc avec son identitĂ© et surtout ses privilĂšges 😏).

C’est bien beau tout ça, mais une question (trĂšs ?) importante subsiste
 Comment rĂ©cupĂ©rer ce fameux access token ?

Parlons bien, parlons « Named Pipe »

Minute papillon ! Avant de pouvoir expliquer comment rĂ©cupĂ©rer un access token, il est nĂ©cessaire de repasser sur certaines bases (encore ? ). Promis, c’est la derniĂšre fois !

Traditionnellement, les techniques « Potatoes » (Hot Potato, Sweet Potato, Local Potato, etc.) utilisent des fonctions RPC pour forcer l’utilisateur NT AUTHORITY\SYSTEM à s’authentifier sur un proxy local que l’attaquant contrĂŽle, puis Ă  relayer cette authentification jusqu’à rĂ©cupĂ©rer un impersonation token du compte SYSTEM. Mais l’objectif de l’article n’est pas de revoir ces techniques bien connues !

Il existe en fait un autre moyen pour aboutir au mĂȘme rĂ©sultat : l’utilisation de « Named Pipe ».

René Magritte - Ceci n'est pas une pipe - Museum TV

D’aprĂšs la documentation de Microsoft, un « pipe est une section de mĂ©moire partagĂ©e qui traite la communication entre un serveur pipe et un client. Le processus qui crĂ©e le pipe est un serveur pipe. Un processus qui se connecte au pipe est un client. Un processus Ă©crit des informations dans le pipe, puis l’autre processus lit les informations du pipe. Cette vue d’ensemble dĂ©crit comment crĂ©er, gĂ©rer et utiliser des pipes. »

https://learn.microsoft.com/en-us/windows/win32/ipc/pipes

Pour rĂ©sumer, les pipes permettent l’échange de donnĂ©es inter-processus (IPC). Sous Windows, il existe deux types de pipe :

  1. Anonymous pipe : Les “Anonymous pipes” transfĂšrent les donnĂ©es entre un processus parent et un processus enfant.

  2. Named pipe : Les “Named pipes” transfĂšrent des donnĂ©es entre des processus qui n’ont pas de lien de parentĂ©, Ă  condition qu’il ait les privilĂšges appropriĂ©s pour interagir avec le processus.

Dans cet article ce qui nous intéresse, ce sont les named pipe. Pourquoi ?

Parce qu’un processus ayant crĂ©Ă© un serveur pipe peut utiliser une fonction trĂšs utile, surtout dans notre cas : ImpersonateNamedPipeClient(). Cette fonction permet de nous placer dans le contexte de sĂ©curitĂ© du client contactant le named pipe ! La principale condition pour pouvoir l’utiliser est de possĂ©der le privilĂšge SeImpersonatePrivilege
 Parfait, c’est ce que nous allons utiliser ! 😊

https://learn.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-impersonatenamedpipeclient

Cette fonction permet au serveur pipe recevant une connexion entrante d’un client (par exemple d’un autre processus) d’emprunter l’identitĂ© du client pour effectuer des actions en son nom, dans son contexte de sĂ©curitĂ©, en utilisant son access token.

Typiquement, dans l’exemple ci-dessous, nous crĂ©ons un serveur pipe accessible via le Named Pipe \.\pipe\mynamedpipe.

Puis, lorsqu’un utilisateur se connecte Ă  ce serveur pipe, nous rĂ©cupĂ©rons les informations liĂ©es Ă  son access token. Dans l’exemple ci-dessous, nous nous connectons au serveur pipe avec l’utilisateur lab\advens.

Donc, pour rĂ©sumer, si nous disposons les privilĂšges requis et parvenons Ă  forcer l’utilisateur NT AUTHORITY\SYSTEM à s’authentifier sur un serveur pipe que nous contrĂŽlons, nous sommes en mesure d’exĂ©cuter des processus en son nom, et donc du code (c’est pas beau ça ? 😊).

ConcrĂštement, c’est exactement ce qu’a expliquĂ© @Itm4n dans son blog. En utilisant la vulnĂ©rabilitĂ© PrinterBug, l’outil PrintSpoofer permet d’élever ses privilĂšges et obtenir des droits NT AUTHORITY\SYSTEM Ă  partir d’un compte disposant notamment du privilĂšge SeImpersonatePrivilege.

Pour ne pas simplement paraphraser son article passionnant, je vous invite Ă  le lire si vous n’ĂȘtes pas particuliĂšrement familier avec l’outil PrintSpoofer.

https://itm4n.github.io/printspoofer-abusing-impersonate-privileges/

Le PrinterBug exploite une « fonctionnalité » implĂ©mentĂ©e dans l’interface RPC MS-RPRN en appelant la procĂ©dure RPC RpcRemoteFindFirstPrinterChangeNotificationEx, qui permet d’envoyer une notification d’impression Ă  un serveur d’impression. Pour mieux comprendre, cette fonction RPC peut ĂȘtre dĂ©tournĂ©e pour forcer une machine Ă  s’authentifier oĂč l’on veut, simplement en indiquant un chemin vers un (faux) serveur d’impression (situĂ© sur un Named Pipe par exemple), ce qui peut ĂȘtre utile dans le cadre d’autres exploits (Voir cet article).

NĂ©anmoins, ce qui nous intĂ©resse ici, c’est le bug utilisĂ© aprĂšs exploitation de la vulnĂ©rabilitĂ© PrinterBug. Celui-ci rĂ©side dans un problĂšme d’interprĂ©tation des « / » par le systĂšme Windows. Je m’explique :

Lorsque la procĂ©dure RPC RpcRemoteFindFirstPrinterChangeNotificationEx est appelĂ©e, le processus spoolsv.exe, qui est dĂ©marrĂ© dans le contexte de sĂ©curitĂ© de l’utilisateur NT AUTHORITY\SYSTEM, vĂ©rifie le chemin spĂ©cifiĂ© par l’utilisateur. Si le named pipe indiquĂ© n’est pas de la forme \somewhere\pipe\spoolss, une erreur est renvoyĂ©e, sinon, il tente de s’y connecter.

Par dĂ©faut, il n’est pas possible de crĂ©er un named pipe dĂ©jĂ  existant, donc pas possible d’écouter sur \localhost\pipe\spoolss ! En revanche, lorsque le chemin spĂ©cifiĂ© est de la forme \somewhere/pipe/controlled, alors le chemin spĂ©cifiĂ© est considĂ©rĂ© comme valide (oui) et il est finalement corrigĂ© par le systĂšme qui ajoute \pipe\spoolss Ă  la fin. Par consĂ©quent, une connexion est effectuĂ©e sur \somewhere\pipe\controlled\pipe\spoolss.

Dans le cadre de Printspoofer, la connexion effectuĂ©e via le processus spoolsv.exe, donc dans le contexte de sĂ©curitĂ© du compte NT AUTHORIY\SYSTEM, se fait localement sur \localhost\pipe\controlled\pipe\spoolss. Bingo ! C’est un named pipe sur lequel il est possible d’écouter.

Pour résumer, grùce à ce bug, il est possible de récupérer un access token associé au compte NT AUTHORITY\SYSTEM, via une connexion sur un Named Pipe que nous contrÎlons. DÚs lors, il est possible de démarrer un cmd.exe avec les privilÚges SYSTEM !

Mais que se passerait-il si le spooler d’impression Windows n’est pas activĂ© sur la machine ? 

C’est lĂ  qu’interviennent des techniques de coercition d’authentification plus rĂ©cemment dĂ©couvertes et notre outil : CoercedPotato.

Un peu de Coercition d’authentification

En 2021, la vulnĂ©rabilitĂ© PetitPotam a permis de dĂ©voiler au grand jour la possibilitĂ© de forcer une machine Ă  s’authentifier n’importe oĂč sur le rĂ©seau, notamment via la fonction RPC EfsRpcOpenFileRaw implĂ©mentĂ©e par l’interface RPC MS-EFSRPC. Cette fonction permet l’ouverture d’un objet chiffrĂ© sur un serveur, afin d’effectuer une sauvegarde ou de la restaurer.

Dans le courant de l’annĂ©e 2022, le travail de P0dalirius a montrĂ© qu’il existe une multitude de fonctions RPC pouvant ĂȘtre exploitĂ©es pour forcer des authentifications grĂące Ă  son outil Coercer (https://github.com/p0dalirius/Coercer).

De plus, de nombreuses mĂ©thodes n’ont pas encore Ă©tĂ© testĂ©es, mais pourraient ĂȘtre exploitĂ©es pour forcer une authentification : https://github.com/p0dalirius/windows-coerced-authentication-methods.

L’idĂ©e nous est donc venue de la combinaison des techniques utilisĂ©es par l’outil PrintSpoofer associĂ©es aux fonctions RPC vulnĂ©rables remontĂ©es par @P0dalirius.

Notre outil a ainsi pour vocation de regrouper toutes les méthodes de coercition en local permettant une élévation de privilÚges à partir des privilÚges SeImpersonatePrivilege et SeAssignPrimaryToken.

Un peu de code maintenant (C++ on fire) !

En combinant les concepts expliquĂ©s prĂ©cĂ©demment, nous avons donc crĂ©Ă© l’outil CoercedPotato qui exploite le privilĂšge SeImpersonatePrivilege ou SeAssignPrimaryToken pour compromettre une machine Windows.

Rentrons dans le dur maintenant ! 

Ouverture d’un serveur pipe

La premiĂšre Ă©tape consiste Ă  lancer un serveur pipe qui attend une connexion sur un named pipe. L’objectif est de rĂ©cupĂ©rer une connexion du compte SYSTEM, donc son access token, et d’exĂ©cuter du code en son nom.

Pour ce faire, dans un nouveau thread, nous lançons les fonctions suivantes :

  1. CreateNamedPipe() – CrĂ©ation d’un serveur pipe en Ă©coute sur le named pipe donnĂ© en paramĂštre. En fonction des appels RPC que nous ferons par la suite, nous Ă©coutons sur un named pipe spĂ©cifique (par exemple : \.\pipe\coerced\pipe\srvsvc).

CreateNamedPipe(lpName, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_WAIT, 10, 2048, 2048, 0, &sa)

  1. ConnectNamedPipe() – Mise du serveur pipe en attente d’une connexion entrante. Cela permet de mettre en pause le thread.

ConnectNamedPipe(hPipe, NULL)

  1. ImpersonateNamedPipeClient() – Une fois une connexion obtenue, nous nous placons dans le contexte de sĂ©curitĂ© du client pour le reste du code exĂ©cutĂ©. La connexion est contenue dans la variable hPipe.

ImpersonateNamedPipeClient(hPipe)

  1. OpenThreadToken() – Lancement d’un nouveau thread dans le contexte de sĂ©curitĂ© du client. Cela n’est possible que si la connexion au serveur pipe a Ă©tĂ© effectuĂ©e avec un impersonation token.

OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, FALSE, &hToken)

  1. CreateProcessWithTokenW() – Dans ce thread, nous venons dĂ©marrer un nouveau processus (par exemple cmd.exe) Ă  l’aide de l’impersonation token. Cela n’est possible qu’avec un impersonation token de niveau 3 ou 4.

CreateProcessWithTokenW(hToken, LOGON_NETCREDENTIALS_ONLY, NULL, newCommandLine, dwCreationFlags, lpEnvironment, lpCurrentDirectory, &si, &pi)

Et voilà ! Nous sommes maintenant capables d’exĂ©cuter du code en tant qu’un autre utilisateur dĂšs lors qu’il se connecte sur notre serveur pipe.

Toutes ces fonctions sont documentées sur le site de Microsoft https://learn.microsoft.com/.

Maintenant que tout est en place, il ne reste plus qu’à forcer le compte NT AUTHORITY\SYSTEM à s’authentifier !

Création du lien RPC

Selon la fonction RPC vulnĂ©rable que nous allons appeler, il peut ĂȘtre nĂ©cessaire de crĂ©er une liaison avec l’interface RPC que nous voulons utiliser : nous devons crĂ©er un RPC binding handle. Une interface RPC pourrait s’apparenter Ă  une classe en programmation orientĂ©e objet. Elle implĂ©mente donc un certain nombre de mĂ©thodes/fonctions. Nous commençons par dĂ©finir la maniĂšre dont la connexion RPC doit ĂȘtre Ă©tablie en appelant la fonction RpcStringBindingCompose() :

RpcStringBindingCompose(nullptr, (RPC_WSTR)L”ncalrpc”, nullptr, nullptr, nullptr, &bindingString);

Cela va permettre de crĂ©er une description de la liaison RPC qui va ĂȘtre Ă©tablie pour spĂ©cifier un certain nombre de paramĂštres. Nous spĂ©cifions d’ailleurs le paramĂštre sequence protocol, ici ncalrpc, qui est un protocole permettant les connexions interprocessus. Le pointeur NULL sur les autres paramĂštres permet une liaison dynamique des interfaces RPC auxquelles se connecter et d’effectuer les connexions en local.

Nous lançons ensuite la fonction RpcBindingFromStringBinding pour effectuer la connexion RPC sur le serveur cible (localhost dans notre cas) et récupérer cette liaison dans la variable Binding.

RpcBindingFromStringBinding(bindingString, &binding_h)

Et voilà ! Nous avons maintenant Ă©tabli une connexion RPC en local. Cette liaison RPC peut ĂȘtre maintenant utilisĂ©e pour appeler des fonctions RPC implĂ©mentĂ©es sur diffĂ©rentes interfaces.

Maintenant que tout est en place, plus qu’à appeler une fonction RPC vulnĂ©rable

La fin de la partie technique est proche, tenez bon ! 😊

Pour faire appel Ă  une fonction RPC en C++, nous devons premiĂšrement disposer d’un client compilĂ© de l’interface ciblĂ©e : pour l’exemple, nous prendrons MS-EFSR. Pour faire simple, pour appeler les fonctions qui nous intĂ©ressent, il faut le code qui implĂ©mente les fonctions RPC, notre client RPC.

C’est lĂ  que ça se complique
 L’objectif est donc de rĂ©cupĂ©rer un fichier IDL (Interface Definition File) dĂ©crivant les fonctions de l’interface RPC. Ce fichier permet de compiler le code pour le client et le serveur. L’auteur @itm4n a (heureusement) Ă©crit un article permettant grandement d’aider les personnes se lançant dans cette quĂȘte : https://itm4n.github.io/from-rpcview-to-petitpotam/.

Finalement, aprĂšs avoir tentĂ© plusieurs techniques compliquĂ©es et non concluantes, il s’est avĂ©rĂ© qu’une mĂ©thode reste la plus fiable : RTFM !

Pour chaque interface RPC, Microsoft a publié le fichier IDL dans la documentation officielle.

https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-efsr/4a25b8e1-fd90-41b6-9301-62ed71334436

Une image contenant texte, capture d’écran, Police Description gĂ©nĂ©rĂ©e automatiquement

Il suffit donc de copier-coller le contenu de l’IDL dans un fichier .idl d’un projet Visual Studio et de le compiler. A force de nous battre avec les problùmes de typages,nous avons fini par trouver une solution plutît simple. Voici notre recette :

  • Une image contenant Visage humain, personne, habits, Humain Description gĂ©nĂ©rĂ©e automatiquementUne fois le contenu du fichier IDL rĂ©cupĂ©rĂ© et collĂ© dans un fichier, retirer la ligne import “ms-dtyp.idl”;. Garder cette ligne gĂ©nĂšre un grand nombre de problĂšmes de typage qui sont fastidieux Ă  dĂ©bugger.

  • Compiler l’IDL pour dĂ©tecter de potentiels problĂšmes de dĂ©finition de types.

Une image contenant texte, capture d’écran, Police, logiciel Description gĂ©nĂ©rĂ©e automatiquement

  • En fonction de ce qui est remontĂ©, ajouter la dĂ©finition en dĂ©but de fichiers. La dĂ©finition de ces types se retrouve ici :

https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/24637f2d-238b-4d22-b44d-fe54b024280c

Une image contenant texte, capture d’écran, Police, algĂšbre Description gĂ©nĂ©rĂ©e automatiquement

  • Continuer ces deux derniĂšres Ă©tapes jusqu’à ce que la compilation fonctionne sans erreur.

Une fois compilĂ©, le fichier IDL permet d’obtenir trois fichiers : ms-efsr_c.c (le client RPC), ms-efsr_s.c (le serveur RPC) et ms-efsr_h.h (fichier d’entĂȘtes). Ceux qui nous intĂ©ressent dans le cadre de l’exploit sont le fichier client RPC et le fichier d’entĂȘtes. Ces fichiers implĂ©mentent donc toutes les fonctions RPC de l’interface MS-EFSR :

Une image contenant texte, capture d’écran, Police, affichage Description gĂ©nĂ©rĂ©e automatiquement

Il ne nous reste plus qu’à l’appeler ! Personnellement, le C et le C++, ce n’est pas ma tasse de thĂ©. Ça tombe bien, bien utilisĂ©, ChatGPT est plutĂŽt douĂ© pour ça ! 😊

Nous allons donc lui demander de nous fournir le code permettant d’initialiser correctement chaque paramùtre pour chaque fonction.

Une image contenant texte, Appareils Ă©lectroniques, capture d’écran, logiciel Description gĂ©nĂ©rĂ©e automatiquement

Et voilà ! Toutes les fonctions sont implĂ©mentĂ©es ! Il ne reste plus qu’à les appeler pour forcer l’utilisateur NT AUTHORITY\SYSTEM Ă  s’authentifier sur notre named pipe en Ă©coute.

C’est Ă  ce moment-lĂ  que le bug liĂ© aux « / » va faire en sorte qu’un processus dĂ©marrĂ© par NT AUTHORITY\SYSTEM (dans notre cas, lsass.exe) se connecte sur un named pipe arbitraire. Par exemple, dans le cas de la fonction EfsRpcOpenFileRaw, nous plaçons notre payload dans le paramĂštre FileName, qui correspond au fichier chiffrĂ© que le serveur doit ouvrir pour rĂ©aliser ou restaurer une sauvegarde. En l’occurrence, nous lui indiquons le fichier \127.0.0.1/pipe/coerced\C$\x00.

Par exemple, pour la fonction EfsRpcOpenFileRaw(), nous définissons le payload de la sorte :


LPWSTR targetedPipeName;

targetedPipeName = (LPWSTR)LocalAlloc(LPTR, MAX\_PATH \* sizeof(WCHAR));

StringCchPrintf(targetedPipeName, MAX\_PATH,
L"\\\\127.0.0.1/pipe/coerced\\C$\\\x00");

long flag = 0;

PVOID pContext;

result = EfsRpcOpenFileRaw(Binding, &pContext, targetedPipeName, flag);

Comme expliquĂ© prĂ©cĂ©demment, Ă  cause une mauvaise interprĂ©tation du systĂšme Windows, une requĂȘte est effectuĂ©e sur le fichier \127.0.0.1\pipe\coerced\pipe\srvsvc par le compte NT AUTHORITY\SYSTEM.

Une image contenant capture d’écran, texte, logiciel, Logiciel multimĂ©dia Description gĂ©nĂ©rĂ©e automatiquementUne image contenant capture d’écran, texte, logiciel, Logiciel multimĂ©dia Description gĂ©nĂ©rĂ©e automatiquement

GrĂące Ă  notre serveur pipe, nous rĂ©cupĂ©rons l’authentification et nous lançons un nouveau processus “cmd.exe” !

Une image contenant texte, capture d’écran, Police, logiciel Description gĂ©nĂ©rĂ©e automatiquement

That is all folks 😊. Et en prime, un petit schĂ©ma rĂ©capitulatif de l’attaque !

Finalement, CoercedPotato !

Nous avons finalement abouti Ă  la crĂ©ation d’un outil Ă©largissant ce comportement sur l’ensemble (ou presque) des fonctions RPC connues pour ĂȘtre vulnĂ©rables.

Ainsi, il est possible de choisir de maniĂšre prĂ©cise quelle fonction RPC utiliser, ou de toutes les forcer afin d’en trouver une valide.

Une image contenant texte, capture d’écran, Police, conception Description gĂ©nĂ©rĂ©e automatiquement

A date de l’article, seules les interfaces suivantes sont exploitables :

  • Des fonctions implĂ©mentĂ©es sur l’interface MS-RPRN ;

  • Des fonctions implĂ©mentĂ©es sur l’interface MS-EFSR.

La finalitĂ© de CoercedPotato est de parcourir l’ensemble de ces mĂ©thodes de coercition jusqu’à en trouver une qui fonctionne.

Avancement de notre recherche : petite désillusion

Comme indiquĂ© prĂ©cĂ©demment, P0dalirus a rassemblĂ© un ensemble de fonctions RPC vulnĂ©rables pour forcer une authentification d’un compte machine sur le rĂ©seau, le tout dans l’outil Coercer (https://github.com/p0dalirius/Coercer). Ce projet est notamment accompagnĂ© d’un autre projet qui rĂ©fĂ©rence toutes les fonctions RPC potentiellement vulnĂ©rables, mais qui n’ont pas encore Ă©tĂ© testĂ©es, soient plus de 240 fonctions
 (https://github.com/p0dalirius/windows-coerced-authentication-methods)

D’instinct, nous sommes partis du principe que toutes ces mĂ©thodes seraient exploitables dans le cadre d’une escalade de privilĂšges en local. Mais
 c’est plus compliquĂ© que ça !

Les fonctions RPC vulnĂ©rables qui sont aujourd’hui identifiĂ©es ont toutes un point commun : elles prennent en entrĂ©e le chemin d’un fichier qui est censĂ© ĂȘtre requĂȘtĂ© par un processus lancĂ© par le compte SYSTEM.

Dans le cadre de MS-RPRN, c’est le processus spoolsv.exe qui effectue une requĂȘte sur le named pipe. Pour MS-EFSR, c’est lsass.exe.

Maintenant, prenons d’autres interfaces qui n’ont pas encore Ă©tĂ© testĂ©es, par exemple MS-EVEN. Cette interface RPC est implĂ©mentĂ©e par le processus svchost.exe dans le contexte de sĂ©curitĂ© de l’utilisateur NT AUTHORITY\LOCAL SERVICE, soit un compte local disposant du niveau de privilĂšges limitĂ©s.

Une image contenant texte, capture d’écran, affichage, nombre Description gĂ©nĂ©rĂ©e automatiquement

Par consĂ©quent, forcer ce processus Ă  effectuer une authentification sur un named pipe que nous contrĂŽlons n’a pas forcĂ©ment de sens dans notre quĂȘte d’élĂ©vation de privilĂšges, puisque nous rĂ©cupĂ©rons une connexion du compte NT AUTHORITY\LOCAL SERVICE.

Une image contenant texte, logiciel, Police, Page web Description générée automatiquement

Toutes les fonctions RPC des interfaces RPC implĂ©mentĂ©es par des processus lancĂ©s dans le contexte de sĂ©curitĂ© d’utilisateurs Ă  faibles privilĂšges ne sont donc pas intĂ©ressantes dans notre cas.

Prenons ensuite le cas de MS-SRVS. Cette interface RPC est bien implĂ©mentĂ©e par un processus lancĂ© en tant que SYSTEM. Mais ce n’est forcĂ©ment pas suffisant !

Prenons l’une de ses fonctions RPC telles que dĂ©finies dans la documentation Microsoft : NetrFileGetInfo().

Une image contenant texte, Police, capture d’écran, blanc Description gĂ©nĂ©rĂ©e automatiquement

Elle prend en paramĂštre 4 variables : ServerName, soit l’adresse serveur qui peut ĂȘtre un named pipe, FileId, soit l’ID d’un fichier (inconnu dans notre cas), Level, soit le niveau d’information que nous voulons rĂ©cupĂ©rer et InfoStruct, soit la variable qui recueille les informations du fichier. Nous Ă©crivons ainsi le code suivant permettant d’appeler cette fonction :


long callNetrFileGetInfo(wchar\_t\* targetedNamedPipe){  
    HRESULT hr;  
    DWORD level = 2;  
    LPFILE\_INFO InfoStruct = NULL;  
    DWORD fileId = 1;  
     
    RpcTryExcept  
    {  
        hr = NetrFileGetInfo(targetedNamedPipe, fileId, level,
InfoStruct);  
    }  
    RpcExcept(EXCEPTION\_EXECUTE\_HANDLER);  
    {  
        hr = RpcExceptionCode();  
        std::cerr &lt;&lt; "\[-\] An error has occurred during
NetrFileGetInfo() : " &lt;&lt; hr &lt;&lt; std::endl;  
    }  
    RpcEndExcept;  
    return hr;  
}

Nous pourrions penser qu’il suffit de rĂ©pĂ©ter l’exploit prĂ©cĂ©dent en injectant notre payload dans ServerName
 Mais non ! La connexion sur le named pipe est effectuĂ©e par l’utilisateur qui a lancĂ© l’outil, soit nous-mĂȘmes.

Une image contenant texte, logiciel, Page web, IcĂŽne d’ordinateur Description gĂ©nĂ©rĂ©e automatiquement

Exploiter cette fonction en indiquant un emplacement sur le rĂ©seau pourrait fonctionner pour provoquer une authentification sur le rĂ©seau, dans la mesure oĂč c’est le compte machine qui prendrait le relai et effectuerait la connexion. Mais en local, c’est un « auto-pwn » !  â˜č

Pour finir l’illustration de nos propos, continuons maintenant avec la fonction NetrpGetFileSecurity().

Une image contenant texte, capture d’écran, Police, blanc Description gĂ©nĂ©rĂ©e automatiquement

Le code suivant a été utilisé :


// shareName doit correspondre à un partage réseau valide.

long callNetrpGetFileSecurity(wchar\_t\* shareName) {  
    long result = 0;  
  
    wchar\_t\* serverName;  
    serverName = (wchar\_t\*)LocalAlloc(LPTR, MAX\_PATH \*
sizeof(WCHAR));  
    StringCchPrintf(serverName, MAX\_PATH, L"localhost");  
  
    wchar\_t\* lpFileName;  
    lpFileName = (wchar\_t\*)LocalAlloc(LPTR, MAX\_PATH \*
sizeof(WCHAR));  
    StringCchPrintf(lpFileName, MAX\_PATH, L"foo1234");  
  
    SECURITY\_INFORMATION RequestedInformation =
OWNER\_SECURITY\_INFORMATION | GROUP\_SECURITY\_INFORMATION |
DACL\_SECURITY\_INFORMATION;  
    PADT\_SECURITY\_DESCRIPTOR SecurityDescriptor = NULL;  
  
  
    result = NetrpGetFileSecurity(serverName, shareName, lpFileName,
RequestedInformation, &SecurityDescriptor);  
    wprintf(L"NetrpGetFileSecurity returned %lx\n", result);  
    return result;  
}

Pour utiliser cette fonction, il est nĂ©cessaire d’utiliser un partage rĂ©seau valide. Une erreur est renvoyĂ©e le cas contraire. Une fois cette condition remplie, il est effectivement possible de forcer l’exĂ©cution d’une requĂȘte par l’utilisateur NT AUTHORITY\SYSTEM. Petit bĂ©mol : le chemin indiquĂ© correspond Ă  un chemin de fichier absolu


Une image contenant texte, logiciel, Page web, IcĂŽne d’ordinateur Description gĂ©nĂ©rĂ©e automatiquement

Cette fonction ne peut donc pas ĂȘtre utilisĂ©e pour Ă©lever nos privilĂšges en local.

Pour rĂ©sumer, la recherche de fonctions vulnĂ©rables pour une Ă©lĂ©vation de privilĂšges en local requiert finalement plus de prĂ©requis que prĂ©vu. Certaines interfaces RPC sont exploitables pour de la coercition d’authentification sur le rĂ©seau, mais pas en local. Pour autant, nous continuons de chercher de nouvelles mĂ©thodes vulnĂ©rables ! 

En revanche, les fonctions actuellement implĂ©mentĂ©es dans notre outil n’ont pas Ă©tĂ© patchĂ©es et ne seront certainement pas patchĂ©es, dans la mesure oĂč leurs comportements sont considĂ©rĂ©s comme « lĂ©gitimes » par Microsoft.

Vous retrouvez le code de l’outil ici : https://github.com/hackvens/CoercedPotato.

Notre PoC a Ă©tĂ© testĂ© sur Windows 10, Windows Server 2016, Windows Server 2022 et Windows 11 ! đŸ„ł

Et voilà, vous savez tout à propos de CoercedPotato !

Remerciements

Nous souhaiterions remercier toutes celles et ceux qui nous ont apporté leurs aides durant nos recherches et plus particuliÚrement :

  • RĂ©mi GASCOU (@Podalirius) pour ses travaux sur l’utilisation d’appels RPC et la crĂ©ation de l’outil Coercer.

  • ClĂ©ment LABRO (@itm4n) pour ses articles et recherches sur Printspoofer et Petitpotam.

  • Guillaume DAUMAS (@BlackWasp) pour ses relectures et conseils.

  • Advens pour l’organisation de la Hackvens ainsi que le temps allouĂ© Ă  nos recherches.

đŸ„”

Un article de Raphaël HUON et Théo BERTRAND