articles / Coerced Potato
October 9, 2023CoercedPotato - Une patate de plus ! đ„
Table des matiĂšres
2. Une histoire de privilĂšges
4. Parlons bien, parlons « Named Pipe »
5. Un peu de Coercition dâauthentification
6. Un peu de code maintenant (C++ on fire)Â !
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.
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.
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 :
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. »
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.
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 :
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.
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 ».
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 :
-
Anonymous pipe : Les âAnonymous pipesâ transfĂšrent les donnĂ©es entre un processus parent et un processus enfant.
-
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 ! đ
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 :
- 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)
- ConnectNamedPipe() â Mise du serveur pipe en attente dâune connexion entrante. Cela permet de mettre en pause le thread.
ConnectNamedPipe(hPipe, NULL)
- 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)
- 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)
- 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.
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 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.
- 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 :
- 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 :
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.
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.
GrĂące Ă notre serveur pipe, nous rĂ©cupĂ©rons lâauthentification et nous lançons un nouveau processus âcmd.exeâ !
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.
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.
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.
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().
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 << "\[-\] An error has occurred during
NetrFileGetInfo() : " << hr << 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.
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().
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âŠ
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