ESC17 - Au-delà de WSUS : détournement de session Kerberos sur LDAPS
Publié le 30 min de lecture
Une série de recherche par Alexis MARTIN (qu35t) et Alexis PARET (nnino). Chaque technique présentée dans cette série a été reproduite de bout en bout dans un environnement de laboratoire entièrement à jour (DC Windows Server 2025 pleinement patché, au 25 juillet 2026).
Le premier volet a posé la primitive : ESC17 fabrique une identité de serveur TLS approuvée à l’échelle du domaine, pour n’importe quel nom d’hôte. WSUS en était la démonstration la plus spectaculaire, parce qu’une mise à jour manipulée y devient du code SYSTEM. Ce troisième volet s’attaque à un service bien plus central, et bien plus subtil : l’annuaire lui-même, en LDAP / LDAPS.
L’intuition naïve serait : « je termine le TLS avec mon certificat ESC17, donc je lis tout l’annuaire ». La lecture passive du trafic est en effet possible, mais l’essentiel est ailleurs. Il se joue dans l’authentification : que devient un bind LDAP quand on lui retire son transport TLS ? La réponse n’est pas binaire. Elle dépend du type de bind et de deux réglages du contrôleur de domaine. Et au bout de cette analyse se trouve un résultat que nous ne pensions pas trouver : le détournement d’une session Kerberos déjà authentifiée, un mécanisme réputé inattaquable.
LDAP est une pile à deux couches
La confusion classique consiste à traiter « LDAPS » comme un bloc monolithique. Ce n’en est pas un. Une session LDAP authentifiée superpose deux couches indépendantes :
- Le transport, soit du LDAP en clair (389/tcp), soit un canal TLS : LDAPS (636/tcp) ou StartTLS sur 389.
- Le bind applicatif, la preuve d’identité négociée à l’intérieur de la session : soit un simple bind (DN + mot de passe), soit un bind SASL via GSS-SPNEGO, qui encapsule à son tour NTLM ou Kerberos.
ESC17 agit sur la couche 1 : on termine le TLS avec un certificat approuvé, on voit le trafic en clair. La vraie question, celle qui décide de l’impact, est : la couche 2 survit-elle à un transport compromis ? Autrement dit, l’authentification apporte-t-elle sa propre protection d’intégrité, indépendante du TLS, ou fait-elle aveuglément confiance au canal ?
La question qui gouverne tout cet article Un bind LDAP protège-t-il ses messages lui-même (signature/scellement applicatif), ou délègue-t-il entièrement la protection au TLS ? Si c’est la seconde option, et que le TLS est à nous, alors la session authentifiée est à nous aussi.
Deux réglages de politique côté DC arbitrent cette question. Il faut les avoir en tête avant tout le reste.
Les deux protections : LDAP signing et channel binding
LDAP signing (LDAPServerIntegrity). Contrôlé par la clé HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Parameters\LDAPServerIntegrity (1 = aucune exigence, 2 = signature requise, valeur par défaut renforcée sur les DC modernes). Quand la signature est requise, un bind SASL sur un transport en clair (389) doit négocier une couche de sécurité GSS (signature/scellement), faute de quoi le DC le rejette. C’est ce qui protège l’annuaire d’un simple sniffing sur 389.
Le point décisif, et contre-intuitif, tient en une phrase du protocole Microsoft. [MS-ADTS], section SASL Authentication :
Active Directory permet d’effectuer des binds SASL sur une connexion protégée par SSL/TLS, [mais] il n’autorise pas l’utilisation de mécanismes de chiffrement/vérification d’intégrité de couche SASL sur une telle connexion.
Concrètement, sur LDAPS (636), la couche de sécurité GSS est interdite. Le DC considère que le TLS fournit déjà la protection, donc le bind SASL, NTLM comme Kerberos, s’effectue sans signature ni scellement applicatif. Toute la protection du trafic post-bind repose alors exclusivement sur le TLS. Or ce TLS, avec ESC17, est le nôtre.
LDAP channel binding (LdapEnforceChannelBinding). Contrôlé par ...\NTDS\Parameters\LdapEnforceChannelBinding (0 = jamais, 1 = si supporté, 2 = toujours). C’est le vrai garde-fou contre ESC17 en LDAPS. Quand il vaut 2, le client intègre dans son jeton d’authentification un Channel Binding Token, à savoir le tls-server-end-point, c’est-à-dire le hash du certificat TLS réellement présenté par le serveur. Le DC vérifie ce hash contre son propre certificat. Si nous avons terminé le TLS avec notre certificat ESC17, le CBT calculé par le client vaut hash(cert ESC17) ≠ hash(cert réel du DC) → le bind est rejeté, quels que soient nos efforts. Le channel binding lie l’authentification au certificat exact du serveur, et c’est précisément la mitigation que Microsoft a poussée avec l’avis ADV190023.
Le tableau de vérité qui en découle, et qui structure toute la suite :
| Réglage DC | Effet sur l’attaque ESC17 en LDAPS |
|---|---|
Signing LDAPServerIntegrity=2 (requis) | Ne mord pas sur 636 : le TLS satisfait l’exigence, la couche SASL est de toute façon absente. Ne bloque que le sniffing sur 389 en clair. |
Channel binding LdapEnforceChannelBinding=0 (jamais) | Aucune protection : rien ne lie l’auth à notre faux certificat. |
Channel binding LdapEnforceChannelBinding=2 (toujours) | Blocage total : notre certificat ESC17 ≠ certificat du DC → bind rejeté (NTLM et Kerberos). |
Tout ce qui suit a été validé dans la configuration la plus fréquente en entreprise, et pourtant suffisante : signature requise (=2), channel binding désactivé (=0). Un durcissement à moitié fait, exactement le genre d’écart que la couche 2 est censée rattraper, mais ne rattrape pas.
Deux protections, deux attaques : laquelle bloque quoi
Le détail le plus important à saisir, et le plus souvent confondu, c’est que ces deux réglages ne protègent pas de la même attaque :
| Réglage DC | Ce qu’il bloque |
|---|---|
Channel binding (LdapEnforceChannelBinding=2) | notre détournement de session (MITM ESC17 sur LDAPS) |
LDAP signing (LDAPServerIntegrity=2) | le relais NTLM vers LDAP |
Ce sont deux protections orthogonales, contre deux attaques distinctes. D’où la matrice de ce qui reste ouvert :
| Signing | Channel binding | Ce qui reste possible |
|---|---|---|
| requis | requis | rien des deux (durcissement complet) |
| requis | absent | détournement de session (notre cas de lab, hijack NTLM & Kerberos) |
| absent | requis | relais NTLM → LDAP |
| absent | absent | les deux : hijack et relais |
Notre laboratoire est la 2ᵉ ligne : signing requis (le relais est fermé) mais channel binding absent (le hijack est ouvert). C’est ce que le reste de l’article exploite.
Et si le LDAP signing est absent (lignes 3-4) ? Une autre voie s’ouvre, indépendante d’ESC17 : le relais NTLM vers LDAP, le scénario ntlmrelayx classique (la raison même de l’avis ADV190023). Sans exigence de signature sur ldap:// (389), et sans channel binding sur ldaps:// (636), une authentification NTLM coercée (PetitPotam, PrinterBug, WebDAV…) peut être relayée vers le LDAP du DC et y exécuter des écritures privilégiées en tant que la victime coercée : RBCD, ajout de machine, octroi de DCSync, Shadow Credentials (msDS-KeyCredentialLink), modification d’ACL. Aucun certificat, aucune terminaison TLS : c’est souvent le chemin le plus rapide vers le domaine.
Ce relais est un angle complémentaire, avec ses propres contraintes :
- NTLM uniquement. Kerberos n’est pas relayable (l’AP-REQ est lié à la clé du SPN cible). En environnement Kerberos-only / NTLM désactivé, le relais tombe, et il reste le détournement ESC17 (qui, lui, couvre Kerberos).
- Il faut coercer une authentification entrante, et l’on est borné par les droits de l’identité coercée (relayer un compte machine privilégié → RBCD → prise de contrôle, relayer un utilisateur lambda → impact limité).
- Il n’aide pas sur LDAPS si le channel binding est activé : la protection du relais NTLM→LDAPS est exactement le même CBT que celui de notre hijack.
Retenons-le : le channel binding protège du détournement, le LDAP signing protège du relais, il faut activer les DEUX. En désactiver un seul laisse une attaque grande ouverte. Les désactiver tous les deux (encore courant) offre les deux voies à la fois.
Le scellement GSS, ce qui décide du détournement
Tout l’article tient sur un concept qu’il faut poser clairement : le scellement (GSS sign & seal). Quand un bind SASL (NTLM ou Kerberos) réussit, les deux parties dérivent une clé de session à partir de l’authentification. Si une couche de sécurité GSS est négociée, chaque message LDAP post-bind est ensuite signé et chiffré avec cette clé (GSS_Wrap) : le trafic devient opaque et infalsifiable pour quiconque n’a pas la clé, y compris un MITM qui possède pourtant le TLS.
La règle est donc la suivante :
Une session authentifiée est détournable si, et seulement si, son trafic post-bind n’est PAS scellé, et que nous possédons le transport.
Or c’est Active Directory lui-même qui décide s’il y a scellement, et il le fait de façon opposée selon le transport :
| Transport | Scellement GSS post-bind ? | Pourquoi | Détournable (avec cert ESC17) ? |
|---|---|---|---|
| LDAP 389 (clair) | OUI, systématique | Sans TLS, l’exigence de LDAP signing force la couche de sécurité GSS. Sinon le DC rejette. | ❌ Non, le trafic est scellé, la clé nous manque |
| LDAPS 636 (TLS) | NON, interdit | [MS-ADTS] : AD n’autorise pas la couche SASL sur une connexion TLS. Le TLS est réputé suffisant. | ✅ Oui, post-bind en clair dans le TLS que nous tenons |
Le point essentiel : l’absence de scellement en LDAPS n’est pas une faiblesse accidentelle ni une mauvaise config, c’est une décision de conception de Microsoft (ne pas empiler deux protections). Un bind Kerberos sur 636 est toujours non scellé, par construction. C’est exactement ce qui rend le détournement possible : le TLS, censé être la protection, devient le maillon que ESC17 nous offre.
C’est aussi pourquoi le channel binding est la seule vraie parade : puisque la protection repose entièrement sur le TLS, il faut lier l’authentification au certificat TLS réel, ce que fait le CBT, et ce que notre faux certificat ESC17 ne peut pas satisfaire.
LDAPS ou StartTLS : la même cible
Une question revient souvent : « ma cible fait du LDAP over SSL/TLS, est-ce que c’est du LDAPS, et est-ce concerné ? » Il existe deux façons d’obtenir du TLS sur LDAP, et il faut les distinguer :
- LDAPS (port 636), TLS implicite : le handshake TLS a lieu dès l’ouverture de la connexion, puis tout le LDAP circule à l’intérieur. C’est le « HTTPS du LDAP ».
- StartTLS (port 389), TLS explicite : la connexion s’ouvre en clair sur le 389, puis le client envoie une opération étendue StartTLS (
1.3.6.1.4.1.1466.20037). Si le serveur accepte, le TLS est négocié sur la même socket, et le LDAP bascule dans le tunnel.
Pour l’attaque, une fois le TLS monté, c’est rigoureusement identique. Les deux aboutissent à une session LDAP protégée par TLS, et :
- La clause [MS-ADTS] (pas de couche sign/seal SASL au-dessus de TLS) s’applique de la même manière aux deux → le post-bind est non scellé dans les deux cas → chevauchable à l’identique.
- Le channel binding est la même protection : le CBT est le
tls-server-end-point, c’est-à-dire le hash du certificat TLS présenté, quelle que soit la façon dont le TLS a été établi. Notre certificat ESC17 casse le CBT identiquement sur 636 comme sur un StartTLS 389.
La seule différence est la mécanique d’interception :
| Établissement du TLS | Ce qu’adecrypt fait | |
|---|---|---|
| LDAPS (636) | implicite, dès la connexion | termine le TLS immédiatement avec le cert ESC17 |
| StartTLS (389) | explicite, via l’op étendue StartTLS | intercepte la requête StartTLS, répond en tant que serveur, puis termine la montée TLS avec le cert ESC17 |
adecrypt gère les deux (--ports 636,389) : le chemin 389 détecte le StartTLS, réalise l’upgrade TLS avec le certificat ESC17, puis rejoint exactement le même moteur de chevauchement. Le détournement fonctionne donc à l’identique sur StartTLS.
Deux nuances à connaître :
- 389 sans StartTLS (un bind SASL directement en clair sur 389) → le bind négocie le scellement (LDAP signing) → hors de portée. Seul le chemin StartTLS-puis-bind est chevauchable sur le 389. Autrement dit, sur 389, tout dépend de si le client demande StartTLS avant de s’authentifier.
- Cas propre à StartTLS, le strip : puisque le StartTLS est une négociation en clair, on peut refuser l’upgrade (répondre un échec au StartTLS) pour tenter un downgrade de la session en clair. C’est surtout payant contre un simple bind : le client, retombé en clair, envoie alors son mot de passe que l’on capture directement. Ce strip est impossible contre LDAPS, qui est TLS-only dès le premier octet.
Côté réalité : LDAPS (636) domine les intégrations Windows et les appliances (« Use SSL » dans la config du connecteur AD). StartTLS (389) est très fréquent côté Linux (SSSD, nslcd, clients OpenLDAP, qui font souvent ldap:// + start_tls). Les deux sont des cibles parfaitement valides. Si votre cible « chiffre son LDAP », elle passe par l’un ou l’autre, et notre chevauchement s’applique dans les deux cas (channel binding non forcé).
Cas 1, simple bind : le mot de passe en clair
Le cas le plus simple, et de loin le plus répandu sur les équipements tiers (appliances, imprimantes, applications métier, connecteurs LDAP). Un simple bind transmet le DN et le mot de passe en clair dans le PDU de bind. En 389 pur, n’importe quel sniffer les récupère, c’est pourquoi tant d’intégrations passent « proprement » par LDAPS.
Sauf que LDAPS ne fait que déplacer le secret dans le tunnel TLS, un tunnel que ESC17 nous donne. On termine le TLS avec le certificat ESC17, on déchiffre le bind, le mot de passe apparaît en clair. Le simple bind n’a ni signature applicative, ni channel binding (aucune couche GSS n’est en jeu) : rien ne s’oppose à la capture.
Validé au lab : un compte de service (10.10.20.11, disons une appliance configurée en LDAPS) se lie au DC, et livre son mot de passe instantanément.
[LDAP] 10.10.20.11 : LDAPS 636 - TLS termination (ESC17) -> DC[AUTH] 10.10.20.11 : SIMPLE bind -> CLEARTEXT password captured >>> [LDAP] simple bind CN=svc-app,OU=Services,DC=esc17,DC=local : Password1! (host=10.10.20.11)adecrypt termine le TLS de la connexion LDAPS avec le certificat ESC17, ouvre sa propre connexion vers le vrai DC et relaie. Le premier PDU applicatif est un simple bind, donc un couple (DN, mot de passe) transmis en clair dans le tunnel : le compte CN=svc-app,OU=Services,DC=esc17,DC=local et son mot de passe Password1! tombent directement. L’identifiant est réutilisable, on s’authentifie ensuite partout où ce compte a des droits sans rejouer l’attaque.
Ce que ça vaut Un simple bind sur LDAPS est toujours interceptable dès lors qu’on tient le TLS. Le channel binding ne le protège pas (il ne s’applique qu’aux binds GSS/SASL, or ici il n’y a aucune couche GSS). C’est l’un des acquis les plus immédiats d’ESC17 sur LDAP, et souvent le plus direct, car les comptes de service d’appliances sont fréquemment (sur)privilégiés.
Cas 2, SASL / NTLM : chevaucher la session, ne pas la relayer
Le cas devient ici plus subtil. Un client Windows moderne ne fait pas de simple bind : il fait un bind SASL GSS-SPNEGO, qui négocie NTLM ou Kerberos. Prenons NTLM d’abord.
Le premier réflexe consiste à se dire : « NTLM, je relaie vers ntlmrelayx ». Avant d’expliquer pourquoi on procède autrement, examinons l’obstacle, et ce fameux MIC.
Le MIC (Message Integrity Code). Dans un échange NTLMv2, le message final AUTHENTICATE (Type 3) embarque un MIC : un HMAC-MD5 calculé sur les trois messages NTLM (Negotiate + Challenge + Authenticate), avec la clé de session dérivée du hash NT de l’utilisateur. Son rôle est précisément anti-relais : empêcher un homme-du-milieu de modifier l’échange, typiquement de retirer les drapeaux qui imposent la signature LDAP, pour pouvoir rejouer l’auth ailleurs. Comme le MIC est scellé par une clé qu’on ne possède pas (le hash NT de la victime), on ne peut ni le forger ni le recalculer après avoir trafiqué les messages. Le mechListMIC de SPNEGO fait le même travail une couche au-dessus : il protège l’intégrité de la liste des mécanismes négociés.
« Drop the MIC », CVE-2019-1040 puis CVE-2019-1166. Ces vulnérabilités permettaient exactement de supprimer / contourner le MIC : le relayeur pouvait alors retirer les drapeaux de signature et faire aboutir un relais NTLM cross-protocole (p. ex. une authentification SMB coercée → relayée vers LDAP). Microsoft les a corrigées (juin puis octobre 2019), et ntlmrelayx embarque l’exploit via l’option --remove-mic. Conséquence très concrète : sur un DC non patché, le relais NTLM→LDAP redevient possible, alors que sur un DC à jour, le MIC le bloque. (Et même MIC contourné, sur LDAPS il resterait le channel binding, cf. plus bas.)
Relayer, c’est de toute façon réémettre l’authentification vers une nouvelle session, ce que le MIC/mechListMIC (et, sur LDAPS, le channel binding) sont faits pour empêcher. Nous ne relayons donc pas. Nous faisons l’inverse : un MITM transparent. L’AUTHENTICATE NTLM (Type 3) est transmis verbatim au vrai DC, MIC intact, aucune modification. Le bind réussit légitimement, comme si nous n’étions pas là. Puis, parce que sur LDAPS la couche de sécurité SASL est absente (cf. [MS-ADTS] ci-dessus), le trafic LDAP post-bind est en clair devant nous. À cet instant précis, le bind vient de réussir et le client attend, nous saisissons la connexion vers le DC et y injectons nos propres opérations, en tant que la victime. Aucune clé de session à posséder, aucun MIC à recalculer : nous ne forgeons rien, nous montons sur une session déjà authentifiée.
C’est ce que fait --ldap-shell d’adecrypt : il détecte le premier bind crédentié non scellé, saisit la connexion, et expose un shell LDAP interactif. Validé au lab en chevauchant la session d’un administrateur :
[LDAP] 10.10.20.11 : LDAPS 636 - TLS termination (ESC17) -> DC[SHELL] session seized from 10.10.20.11 -> acting AS the victim on the DC[SHELL] interactive LDAP shell on 0.0.0.0:11337
LDAP# grant_dcsync nninogrant_dcsync nnino -> resultCode=0 (success)Le bind NTLM (Negotiate → Type 1/2/3) est passé verbatim au DC, qui valide : le bind réussit pour de vrai, le MIC n’est jamais touché. Le trafic post-bind arrive alors en clair, faute de scellement GSS sur TLS, et adecrypt saisit la connexion vers le DC. À partir de là, toute opération est vue par le DC comme provenant de l’administrateur. adecrypt expose un shell local (nc <attaquant> 11337) depuis lequel grant_dcsync nnino ajoute à nnino les droits de réplication DS-Replication-Get-Changes[-All] sur l’objet domaine. Le resultCode=0 confirme que le DC accepte l’écriture parce qu’elle vient d’un administrateur : aucune élévation de notre part, on emprunte les droits de la session saisie.
De là, secretsdump.py esc17/nnino@dc01 -just-dc utilise ces nouveaux droits DCSync pour répliquer toute la base NTDS (hashes de tous les comptes, dont krbtgt). Compromission complète du domaine, à partir d’une session NTLM que nous n’avons ni cassée, ni relayée, ni même déchiffrée.
Prérequis, à répéter parce qu’il est central : channel binding désactivé. S’il était sur Always, le Type 3 embarquerait un CBT lié à notre faux certificat, le DC le rejetterait, et le bind verbatim échouerait avant même qu’il y ait une session à chevaucher.
NTLM ou Kerberos : pour le chevauchement, c’est la même chose
Un point à intégrer avant de passer à Kerberos : notre chevauchement est agnostique au mécanisme d’authentification. Une fois le bind passé verbatim et réussi, on injecte des opérations dans une connexion LDAP en clair dans le TLS, et le mécanisme d’origine (NTLM ou Kerberos) n’intervient plus du tout. C’est pour cela qu’un seul et même code (--ldap-shell) traite les deux, avec les mêmes préconditions (pas de seal + channel binding non forcé) et les mêmes limites.
La seule différence NTLM/Kerberos n’existe que sur le terrain, orthogonal, du relais :
- NTLM est relayable : le jeton n’est pas lié cryptographiquement au service cible, on peut le relayer ailleurs. C’est tout l’objet de
ntlmrelayx. - Kerberos ne l’est essentiellement pas : l’AP-REQ contient un ticket chiffré avec la clé du SPN visé, le rediriger vers un autre service échoue (dixit Dirk-jan Mollema : « you cannot actually relay Kerberos the way you can relay NTLM »). James Forshaw a montré des relais Kerberos conditionnels, mais pas comme voie standard vers LDAP.
Sauf que, et c’est le point clé, cette relayabilité de NTLM ne nous ouvre aucun accès supplémentaire en LDAPS : la protection qui arrête un relais NTLM→LDAPS est exactement le channel binding qui arrêterait notre chevauchement. Puisqu’on possède déjà le canal TLS et qu’on monte sur une session légitime, le chevauchement est plus propre que le relais : aucune coercition, aucune bataille MIC / Drop-the-MIC, aucune négociation de drapeaux à trafiquer, et une seule primitive pour NTLM comme pour Kerberos. Le relais reste une option NTLM-only, plus fragile, à réserver au cas où l’on ne peut pas se placer en MITM sur le canal.
Cas 3, SASL / Kerberos : le détournement qu’on ne croyait pas possible
Reste le cas que tout le monde considère hors de portée : Kerberos. Le dogme est solide : un AP-REQ est chiffré vers la clé de la machine cible, on ne peut pas le relayer (Dirk-jan Mollema le dit sans détour : « you cannot actually relay Kerberos the way you can relay NTLM »), et l’outillage public de relais vers LDAP (ntlmrelayx) est NTLM uniquement.
Mais nous ne cherchons pas à relayer. Nous appliquons exactement le raisonnement du Cas 2, et il tient, à l’identique. L’AP-REQ est passé verbatim au vrai DC : le bind Kerberos réussit légitimement, le mechListMIC n’est jamais touché, il n’y a aucun downgrade. Et surtout, la clause [MS-ADTS] s’applique de la même manière : sur LDAPS, le bind Kerberos ne négocie aucune couche de scellement GSS. Il fait confiance au TLS. Le trafic post-bind est donc en clair devant nous, et nous chevauchons la session Kerberos authentifiée.

Comment lire ce schéma : le client croit parler au DC (dc01.esc17.local) et lui présente son AP-REQ Kerberos. adecrypt, au milieu, ne comprend pas ce ticket (il est chiffré vers la clé du DC), mais il n’en a pas besoin : il le recopie tel quel vers le vrai DC. Le DC déchiffre, valide, répond success. Le bind est donc authentique de bout en bout, c’est la clé pour ne rien casser. Ce n’est qu’ensuite, quand le client envoie sa première requête LDAP (en clair, faute de scellement sur TLS), qu’adecrypt bascule : il garde la connexion pour lui et y injecte ses propres opérations, que le DC attribue à la victime.
La preuve, indépendante et vérifiable, tient dans le sens de la couche de sécurité : sur 389 en clair, un bind Kerberos Windows dérive bien des clés de signature/scellement et protège ses messages. Être MITM sur 389 n’apporte rien, les opérations injectées échouent au MIC GSS. C’est uniquement parce que le TLS désactive ce scellement que la terminaison TLS expose la session. La prémisse « pas de scellement parce qu’il fait confiance au TLS » n’est pas un détail : c’est le pivot de toute la technique.
adecrypt matérialise cela avec --ldap-shell, qui chevauche aussi bien les binds NTLM que Kerberos et confirme d’abord l’absence de scellement avant de saisir. Validé au lab (signature requise, channel binding désactivé) :
[LDAP] 10.10.20.11 : LDAPS 636 - TLS termination (ESC17) -> DC[HIJACK] 10.10.20.11 : credentialed bind OK (GSS/Kerberos) -> post-bind CLEARTEXT (no GSS seal over TLS) -> seizing[HIJACK] session #1 seized : u:ESC17\Administrator @ 10.10.20.11 (mechListMIC untouched, AP-REQ verbatim)
LDAP# grant_dcsync nninogrant_dcsync nnino -> resultCode=0 (success)adecrypt voit le bind SASL Kerberos réussir au niveau du DC, puis attend le premier message post-bind, car c’est lui qui révèle si la session est scellée. Il arrive en clair (pas de couche GSS sur TLS, conformément à [MS-ADTS]), donc la session est détournable et adecrypt la saisit, sans avoir touché ni le mechListMIC ni l’AP-REQ : le bind Kerberos reste parfaitement légitime. Comme un bind Kerberos pur ne porte aucun message NTLM d’où tirer le domaine, le shell lit le defaultNamingContext dans le RootDSE au travers de la session saisie, seule chose dont on dispose et qui suffit à interroger l’annuaire. grant_dcsync nnino est alors accepté (resultCode=0) parce que l’écriture vient d’Administrator : de là, un secretsdump.py esc17/nnino@dc01 -just-dc déroule toute la base NTDS. Détournement d’une session Kerberos, jusqu’à la compromission du domaine.
La puissance du résultat est bornée par les droits de la victime : chevaucher la session d’un compte non privilégié donne les droits de ce compte. C’est ce que montre la session ci-dessous.
Une console, plusieurs victimes, les droits de chacune
adecrypt agrège toutes les sessions saisies dans une seule console multi-session : sessions liste les comptes détournés, use <id> bascule de l’un à l’autre, et chaque opération s’exécute avec les droits du compte actif. Par défaut, une session est gardée par identité. --keep-hijack saisit toutes les connexions, ce qui permet ici de récupérer deux comptes différents depuis une même machine (10.10.20.11), à savoir nnino puis Administrator :
[HIJACK] session #1 seized : u:ESC17\nnino @ 10.10.20.11 -> added to the shell pool[HIJACK] session #2 seized : u:ESC17\Administrator @ 10.10.20.11 -> added to the shell poolLDAP# sessions ID IDENTITY SOURCE AGE STATE * #1 u:ESC17\nnino 10.10.20.11 0m30s alive #2 u:ESC17\Administrator 10.10.20.11 0m09s alive
LDAP# grant_dcsync nnino # session active = #1 (nnino, non privilégié)grant_dcsync nnino -> resultCode=50 (insufficientAccessRights)
LDAP# use 2 # on bascule sur la session d'Administratoractive session -> #2
LDAP# grant_dcsync nnino # même commande, mais AS Administrator cette foisgrant_dcsync nnino -> resultCode=0 (success)La démonstration est nette : la même commande échoue (resultCode=50) sous l’identité nnino, puis réussit (resultCode=0) une fois la session active basculée sur Administrator. On n’exploite jamais une élévation de privilèges, on emprunte simplement les droits de la session que l’on chevauche. grant_dcsync nnino accorde à nnino les droits de réplication DS-Replication-Get-Changes[-All] sur l’objet domaine, et secretsdump.py esc17/nnino@dc01 -just-dc déroule alors toute la base NTDS. Compromission du domaine, à partir d’une session Kerberos qu’on n’a ni cassée ni relayée.
Est-ce réaliste ? Qui parle Kerberos en LDAPS ?
Objection légitime : « d’accord, mais dans la vraie vie, qui ouvre une session Kerberos non scellée sur LDAPS ? » La réponse tient en une observation : LDAPS est le mode d’intégration Active Directory par défaut de la quasi-totalité des applications et équipements tiers, et son usage repose entièrement sur l’hypothèse que le certificat du DC est digne de confiance. C’est le fil rouge de cette série, et ESC17 brise cette hypothèse.
Rappel du périmètre : on n’intercepte que le trafic sur 636, les binds sur 389 (scellés) sont hors de portée. Mais le trafic 636 est loin d’être marginal. Chaque fois qu’un de ces clients ouvre une connexion LDAPS, il produit exactement le bind exploitable :
- Applications & appliances qui authentifient leurs utilisateurs contre l’AD : portails intranet, ticketing (GLPI, Jira), CI/CD (GitLab, Jenkins), supervision (Zabbix, Grafana), passerelles VPN et firewalls (Fortinet, Palo Alto…), NAS (Synology/QNAP), imprimantes/MFP,
vCenter… Presque tous proposent « LDAP over SSL » et l’admin l’a coché par souci de sécurité. À chaque login utilisateur, l’appli fait un bind LDAPS. Souvent un simple bind (compte de service) → on récupère au passage le mot de passe en clair (Cas 1), parfois un bind Negotiate/Kerberos → on chevauche la session (Cas 3). - Portails de réinitialisation de mot de passe (SSPR) : écrire
unicodePwdexige un canal confidentiel, et beaucoup de portails choisissent LDAPS pour cela → binds LDAPS à la demande. - Connecteurs IAM / provisioning (synchronisation d’annuaires, gestion des identités) configurés en LDAPS.
- Outils d’audit / scanners en mode authentifié (collecte AD) pointés en LDAPS.
- Administration : un simple
ldp.execonnecté en SSL sur 636 (Bind Negotiate), le déclencheur GUI trivial pour reproduire en laboratoire, sans la moindre alerte de certificat puisque le cert ESC17 est réellement approuvé.
Un point clé pour l’opérateur : ces connexions sont à la demande, déclenchées par un login, un reset ou une synchro, et non soumises à un polling lent comme WSUS. On provoque le trafic en se connectant simplement au service concerné.
Une observation résume la situation : c’est le choix de LDAPS « pour la sécurité » qui rend l’interception discrète. Le certificat étant émis par l’autorité de certification du domaine, un client qui se contente de vérifier la chaîne de confiance ne lève en principe aucune alerte (le comportement dépend de la rigueur de validation côté client).
Récupérer un hash NetNTLMv2 réutilisable (--spnego-downgrade)
Le chevauchement des Cas 2 et 3 suppose une session en clair à saisir sur place. Quand ce n’est pas l’objectif, par exemple lorsqu’on veut simplement repartir avec un identifiant réutilisable à casser tranquillement, ou préparer un accès pour plus tard, adecrypt offre une capacité complémentaire, et parfaitement fiable : forcer le downgrade Kerberos → NTLM pour récupérer le hash NetNTLMv2 de la connexion.
Un client qui parle Kerberos ne laisse, par défaut, rien à cracker : aucun secret réutilisable ne transite. Mais en réécrivant le NegTokenInit du bind pour n’offrir que NTLM, on force le client à retomber sur ce mécanisme. Il émet alors un échange NTLM complet, et son message AUTHENTICATE (Type 3) porte une réponse NetNTLMv2, un défi/réponse crackable hors-ligne (hashcat, mode 5600). On le récupère au vol, dans le tunnel TLS que l’on possède.
[LDAP] 10.10.20.11 : LDAPS 636 - TLS termination (ESC17) -> DC[AUTH] 10.10.20.11 : KERBEROS detected -> forced DOWNGRADE to NTLM[NTLM] 10.10.20.11 : NetNTLMv2 captured -> ./adecrypt_loot/credentials.txt Administrator::ESC17:1122334455667788:9E1C...F0:0101000000000000...Le client voulait faire du Kerberos, mais adecrypt réécrit le premier jeton SPNEGO (NegTokenInit) pour ne proposer que NTLM : croyant le serveur limité à ce mécanisme, le client rejoue son authentification en NTLM. Son message AUTHENTICATE (Type 3) porte alors une réponse NetNTLMv2 dérivée du hash NT du compte, capturée au vol et écrite dans credentials.txt au format hashcat (utilisateur::domaine:challenge_serveur:NTProofStr:blob), crackable hors-ligne en hashcat -m 5600.
À noter : ce hash est capturé que le bind aboutisse ou non, car le Type 3 est émis avant que le DC ne statue, donc même si la vérification mechListMIC finit par rejeter le bind downgradé, le NetNTLMv2 est déjà entre nos mains.
Une fois le hash cassé, on dispose du mot de passe en clair du compte : on peut alors s’authentifier directement, sans même remettre les pieds en MITM. Là où le Cas 3 donne une session vivante immédiate, --spnego-downgrade donne un identifiant durable. Les deux angles se complètent, selon qu’on veut agir tout de suite ou s’installer.
Récapitulatif
Selon le type de bind présenté par le client sur LDAPS, l’issue diffère. Le tableau ci-dessous résume les quatre situations, dans la configuration étudiée (LDAP signing requis, channel binding absent).
| Type de bind | Ce qu’on récupère | Préconditions | Impact |
|---|---|---|---|
| Simple bind | le mot de passe en clair | TLS terminé (LDAPS ou StartTLS) | identifiant réutilisable, souvent un compte de service |
| SASL NTLM | la session authentifiée (chevauchement) | TLS terminé, post-bind non scellé, channel binding absent | actions dans l’annuaire en tant que la victime (DCSync, RBCD, Shadow Credentials, ACL…) |
| SASL Kerberos | la session authentifiée (chevauchement) | idem NTLM | idem NTLM |
NTLM forcé (--spnego-downgrade) | un hash NetNTLMv2 | TLS terminé | hash crackable hors-ligne, donc mot de passe réutilisable |
Le point commun des trois premiers cas : adecrypt ne casse ni ne rejoue l’authentification, il la laisse aboutir puis exploite l’absence de scellement pour agir sur une session légitime. Le quatrième est une solution de secours lorsque le chevauchement n’est pas souhaité.
Est-ce une vulnérabilité Kerberos ? Non. Est-ce une 0-day ? Non plus.
Soyons précis, car la nuance est le cœur du sujet.
Ce n’est pas une faille de Kerberos. La RFC 4120 est explicite, « The application is free to choose whatever protection may be necessary » : l’intégrité et le chiffrement des messages sont optionnels dans Kerberos. La RFC 4752 confirme que « pas de couche de sécurité » est un résultat de négociation parfaitement légal. Le défaut n’est pas dans le protocole, il est dans la façon dont le service utilise Kerberos, sans lier l’authentification au canal. Le principe est d’ailleurs ancien : Black Hat USA 2010, Attacking Kerberos Deployments, le formulait déjà (« you want a tie between authentication and protocol, but have to build it yourself »).
Ce n’est pas une 0-day. Le maillon qui rend l’attaque possible, à savoir « LDAPS MITM sans channel binding permet de prendre la main sur la session », est une classe de faiblesse connue, et Microsoft en livre déjà la mitigation nommée : LdapEnforceChannelBinding=Always (ADV190023). MSRC classerait cela by-design / appliquez le durcissement existant.
Automatisation avec adecrypt
Comme pour les autres volets, adecrypt prend le certificat éligible ESC17 et pilote l’attaque de bout en bout.
# 1. Certificat serveur pour le DC, via le modèle vulnérable à ESC17certipy req -u attacker@esc17.local -p '...' -ca 'ESC17-CA' \ -template 'VulnServer' -dns dc01.esc17.local -out dc01
# 2. MITM LDAPS + shell de chevauchement (NTLM et Kerberos)adecrypt ldaps --targets 10.10.20.11 --gateway 10.10.20.1 --interface eth0 \ --dc 10.10.20.10:dc01.pfx --ports 636 --ldap-shell
# 3. Console multi-session : lister les cibles, basculer, agir EN TANT QUE la victime# nc 127.0.0.1 11337 -> sessions | use <id> | grant_dcsync <user> | set_rbcd | passwd ...--ldap-shell chevauche indifféremment les binds NTLM et Kerberos (il confirme d’abord l’absence de scellement post-bind avant de saisir) et expose une seule console multi-session : sessions liste les comptes détournés, use <id> bascule, et le dédoublonnage garde une session par identité (plusieurs comptes distincts d’une même machine sont chacun saisis). --keep-hijack saisit toutes les connexions, et --spnego-downgrade reste l’option hors-ligne (capture NetNTLMv2) quand le chevauchement n’est pas possible.
Mitigations
Une seule contre-mesure neutralise toute la couche 2 de cet article, NTLM comme Kerberos :
- Imposer le channel binding LDAP :
LdapEnforceChannelBinding = 2(Always). C’est la mitigation décisive, elle lie l’authentification au certificat réel du DC, et notre certificat ESC17 échoue immédiatement. À déployer avant de se soucier du reste. - Corriger le modèle ESC17 à la source : retirer le flag « supply in the request », restreindre les droits d’enrôlement, ou exiger l’approbation d’un gestionnaire, sans se fier à la seule valeur d’EKU.
- Épingler le certificat côté clients LDAPS gérés centralement, quand c’est faisable.
- Surveiller l’émission de certificats Server Authentication portant un SAN arbitraire, et l’apparition de binds LDAPS provenant d’hôtes inattendus.
Et, pour le simple bind (Cas 1), rappeler que le channel binding ne le protège pas : là, seuls comptent la correction du modèle ESC17 et l’épinglage.
À lire ensuite
ESC17 - Au-delà de WSUS : interception du HTTPS interne
ESC17 forge un certificat approuvé par le domaine. Sur le HTTPS interne, il rend en clair identifiants, jetons de session et authentifications Windows.
26 min de lecture
ESC17 - Au-delà de WSUS : introduction et modèle de menace
ESC17 n'est pas une faille WSUS mais un problème de confiance PKI dans ADCS. Premier volet d'une série qui cartographie sa surface d'attaque.
12 min de lecture
ESC17 - Au-delà de WSUS : interception RDP et contournement de NLA
ESC17 supprime l'alerte de certificat RDP. Le downgrade de NLA ne passe plus sur client moderne, mais terminer CredSSP capture le mot de passe malgré NLA.
18 min de lecture