Resource-Based Constrained Delegation - Risques
Cet article montre comment abuser de la délégation contrainte basée sur les ressources (resource-based constrained delegation) afin de prendre le contrôle de machines.
Il repose sur du contenu d’excellente qualité. J’ai tenté de résumer avec des mots simples une petite partie du travail de Elad Shamir. Son article Wagging the Dog: Abusing Resource-Based Constrained Delegation to Attack Active Directory est incroyable de par ses recherches. D’autres articles ont suivi, notamment celui de Dirk-jan (The worst of both worlds: Combining NTLM Relaying and Kerberos delegation ) ou celui de Harmj0y (A Case Study in Wagging the Dog: Computer Takeover), me permettant d’assembler toutes les pièces et de rédiger cet article.
Vous êtes prêts ? Let’s dive in.
Rappels : Resource-Based Constrained Delegation
Dans cet article, le terme “Resource-Based” sera utilisé pour parler de cette délégation.
Contrairement à la délégation complète, la délégation Resource-Based est un poil plus compliquée. L’idée générale est que ce sont les ressources de fin de chaine qui décident si oui ou non un service peut s’authentifier auprès d’elles en tant qu’un autre utilisateur. Ces ressources ont donc une liste de comptes en lesquels elles ont confiance. Si une ressource fait confiance au compte WEBSERVER$
, alors quand un utilisateur s’authentifiera auprès de WEBSERVER$
, il pourra lui même s’authentifier auprès de cette ressource en tant que l’utilisateur.
En revanche, si un autre utilisateur s’authentifie auprès d’un compte qui ne fait pas partie de la liste de confiance du service (par exemple FILESERVER$
ou Sql_SVC
dans le schéma ci-dessous), ce compte ne pourra pas se faire passer pour cet utilisateur auprès du service.
Concrètement, comme expliqué dans l’article sur la délégation, la ressource finale, à droite sur le schéma, a un attribut appelé msDS-AllowedToActOnBehalfOfOtherIdentity
qui contient la liste des comptes en lesquels elle a confiance.
Par ailleurs, pour que cette délégation fonctionne, l’attribut ADS_UF_NOT_DELEGATED
de l’utilisateur ne doit pas être positionné. C’est un attribut qui permet d’interdire la délégation en vue de protéger le compte des attaques liées à la délégation.
Notons également que pour relayer l’authentification d’un utilisateur, le compte de service “relais” (WEBSERVER$
dans le schéma) doit avoir un ticket de service provenant de l’utilisateur.
Si l’utilisateur s’authentifie via Kerberos, aucun soucis, le service “relai” est en possession du ticket de service de l’utilisateur, donc le mécanisme (S4U2Proxy) est simple, le compte de service envoie le ticket de service de l’utilisateur au contrôleur de domaine pour que celui-ci lui renvoie un ticket de service valide pour accéder à la ressource désirée (si bien sûr le compte de service fait partie de la liste de confiance de la ressource).
En revanche, si l’utilisateur s’authentifie d’une autre manière (NTLM par exemple), le compte de service n’a pas reçu de ticket de service. Il va alors devoir faire une première demande pour avoir un ticket de service transférable au nom de l’utilisateur, c’est le mécanisme S4U2Self, puis il utilisera ce ticket de service transférable pour faire le processus expliqué juste avant, S4U2Proxy.
Comportement “by design”
Maintenant que ces rappels sont faits, il est intéressant de creuser un peu. En effet, on se rend compte que le mécanisme S4U2Self peut être assez dangereux, parce qu’un service peut finalement demander un ticket de service au nom de n’importe quel utilisateur. Dans le processus normal, il demande un ticket de service au nom de l’utilisateur qui s’est authentifié auprès de lui, mais rien ne l’oblige à se limiter à ce compte.
Heureusement, cette possibilité de demander des tickets de service transférables au nom de n’importe qui (S4U2Self) n’est possible que pour les comptes ayant l’attribut TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION de positionné, c’est à dire les compte pouvant faire de la délégation contrainte avec transition de protocole. Ce n’est donc pas un attribut positionné par défaut.
Plus précisemment, tous les comptes de service peuvent faire un S4U2Self, mais seuls ceux avec l’attribut TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION recevront un ticket de service transférable, condition à priori nécessaire pour S4U2Proxy (cf. la documentation Microsoft).
Ainsi, par exemple, lors d’une délégation contrainte (pas Resource-Based) avec transition de protocole, si un compte machine demande un ticket de service via S4U2Self, comme cette machine est en délégation contrainte avec transition de protocole, elle possède bien le drapeau TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION. Ce compte recevra un donc ticket de service transférable, et lors de la demande de ticket de service pour une des ressources pour lesquelles il peut faire de la délégation (S4U2Proxy), il pourra transférer ce ticket de service.
En revanche, et c’est là que c’est super incroyable, dans le cas de la délégation Resource-Based, si de la même manière un compte machine demande un ticket de service via S4U2Self, cette fois-ci le compte n’a pas l’attribut TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, ce compte recevra donc un ticket de service qui sera non transférable, mais lors de la demande de ticket de service pour une ressource (S4U2Proxy), cette demande SERA ACCEPTÉE (cf. encore la documentation Microsoft).
Dans cet exemple, le SERVEUR1$
n’a pas l’attribut TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION de positionné, mais il est déclaré dans la liste de confiance de Service B
. Lors de sa demande de ticket de service pour un utilisateur lambda via S4U2Self, le contrôleur de domaine lui envoie un ticket de service non transférable, cependant en passant ce ticket de service à la requête S4U2Proxy afin de récupérer un ticket de service pour utiliser le Service B
, il n’y a aucun soucis, ça fonctionne, et SERVEUR1$
peut utiliser Service B
en tant que l’utilisateur choisi.
Au cas où ce n’est toujours pas clair, ça veut dire que SERVEUR1$
peut s’authentifier auprès de Service B
en tant que n’importe quel utilisateur.
Et ouais.
Attribut Machine-Account-Quota
Pour pouvoir décrire un chemin d’attaque complet, il faut également parler de l’attribut msds-MachineAccountQuota qui est positionné sur le domaine. Cet attribut décrit le nombre de comptes machine qu’un utilisateur du domaine peut créer. Cet attribut vaut 10 par défaut. Cela signifie que, par défaut, un utilisateur peut joindre 10 machines au domaine, ou qu’il peut créer 10 comptes machine, en choisissant notamment leur nom et leur mot de passe.
Ce comportement peut être modifié par GPO, mais voilà, par défaut, c’est 10.
Cet ajout peut être fait directement dans les paramètres d’une machine Windows, mais également via LDAP. Cette fonctionnalité est notamment disponible dans l’outil Powermad de Kévin Robertson.
New-MachineAccount -MachineAccount NOUVELLEMACHINE -Password $(ConvertTo-SecureString "Hackndo123+!" -AsPlainText -Force)
Cette fonctionnalité est importante parce que dans les histoires de délégation, les comptes concernés sont des comptes de service, c’est à dire des comptes avec un ou plusieurs SPN. Un utilisateur du domaine n’a pas d’attribut SPN, mais il peut créer un compte machine qui lui possède par défaut plusieurs SPN.
Exploitation
Nous avons maintenant tous les éléments en main pour décrire un chemin d’attaque. Il en existe d’autres, bien sûr, mais ce chemin permet d’assembler toutes les pièces du puzzle.
Nous nous plaçons dans le cas d’une position man-in-the-middle en utilisant le fait que IPv6 est majoritairement activé dans les réseaux d’entreprise, et que d’un point de vue OS, c’est ce protocole qu’il faut utiliser en priorité, avant IPv4. Un attaquant peut alors se positionner en serveur DHCP IPv6 et répondre aux équipements environnants.
Par ailleurs, il est possible de relayer une authentification HTTP d’une machine en une authentification LDAPS vers une autre. Nous relaierons les connexions vers le contrôleur de domaine.
Lorsque la machine DESKTOP-01
va se connecter au réseau (reboot, branchement du cable réseau), le compte DESKTOP-01$
va se connecter en HTTP à notre machine du fait de notre position man-in-the-middle. C’est à ce moment que le relai va être effectif.
Deux actions vont être effectuées via LDAPS, en utilisant cette authentification relayée, en tant que DESKTOP-01$
.
- Un compte machine
HACKNDO$
va être créé, puisque le compteDESKTOP-01$
est un compte du domaine, et a le droit d’ajouter 10 comptes machine au domaine, par défaut. - Le compte machine
DESKTOP-01$
a le droit de modifier certains de ses attributs, dont l’attributmsDS-AllowedToActOnBehalfOfOtherIdentity
, qui est la liste des machines de confiance pour la délégation “Resource-Based”. On va donc modifier cet attribut pour ajouterHACKNDO$
à cette liste de confiance.
Maintenant, nous avons la main sur le compte HACKNDO$
, puisque nous avons choisi son nom et son mot de passe lors de la création, et ce compte a la confiance de DESKTOP-01$
.
Il suffit alors de se connecter en tant que HACKNDO$
, faire une demande de ticket de service au nom d’un administrateur via S4U2Self
. Le contrôleur de domaine va nous répondre avec un ticket de service non transférable. Nous demandons ensuite un ticket de service pour utiliser le service CIFS
de DESKTOP-01
(CIFS/DESKTOP-01
) en fournissant le ticket de service précédemment demandé (S4U2Proxy). Comme nous l’avons vu au début, bien qu’il ne soit pas transférable, il est tout de même accepté, et le contrôleur de domaine nous envoie un ticket de service en tant qu’administrateur pour utiliser le service CIFS
de DESKTOP-01
.
Voici un petit schéma récapitulatif :
Il est possible de répéter l’opération pour demander un ticket de service en vue de pouvoir gérer les services. Ainsi, en envoyant un exécutable de type “reverse-shell” sur la machine avec le ticket CIFS
, puis en créant et démarrant un service utilisant cet executable à l’aide d’un ticket SCHEDULE
, il est possible de prendre la main sur la machine en tant que SYSTEM.
Résumé
L’opération étant un peu complexe, voici les différentes étapes résumées :
- Ajouter une machine
M
au domaine (via un relai d’authentification, via un compte de domaine déjà possédé, …) - Ajouter cette machine
M
à la liste de confiance de la cibleC
(i.e. l’ajouter à l’attributmsDS-AllowedToActOnBehalfOfOtherIdentity
en relayant l’authentification de la machine cible, puisqu’elle a le droit de changer son propre attribut) - Faire une demande de ticket de service pour un ticket au nom d’un administrateur via S4U2Self
- Une fois en possession de ce ticket, l’envoyer au contrôleur de domaine pour demander un ticket de service vers la cible
C
via S4U2Proxy. - Enjoy ce nouveau ticket valide, en tant que l’administrateur choisi. D’autres tickets peuvent être demandés avec l’étape 4.
Conclusion
La délégation “Resource-Based” est un procédé complexe, tellement complexe que des problématiques de sécurité sont introduites dans l’implémentation de la fonctionnalité.
Ce qu’il faut finalement retenir, c’est qu’il est possible de prendre la main sur une machine lorsqu’on a la possibilité de modifier sa liste de confiance dans le cadre de la délégation basée sur les ressources.
Pour s’en prémunir, plusieurs actions sont possibles. Tout d’abord, il faut implémenter le channel binding au niveau de LDAP afin d’empêcher le relai vers LDAPS. Ensuite, il faut limiter la possibilité de création de comptes machine sur le domaine à des groupes définis d’utilisateurs. Enfin, en lien avec cet exemple, IPv6 devrait être désactivé dans les réseaux d’entreprise, puisqu’il ne répond à aucun besoin.
Cet article étant relativement dense, il serait tout à fait normal que des points ne soient pas tout à fait clairs. Si vous avez des questions, n’hésitez pas à les poser en commentaire ou sur Discord. Vous pouvez toujours suivre la sortie de nouveaux articles sur mon twitter @hackanddo.
Enfin, n’hésitez pas à faire un tour sur les ressources citées, notamment l’article de Elad Shamir qui est un travail de recherche complet et super intéressant.