Sur la maquette test.local, nous allons provoquer trois pannes Kerberos: un SPN en double, une horloge décalée et un type de chiffrement refusé. Pour chacune, nous lirons ensuite le KRB_ERROR que le KDC renvoie, dans Wireshark puis avec klist.
Dans une capture, le KRB_ERROR passe souvent pour du bruit, alors qu’il porte presque toujours la cause de la panne: le code, le principal demandé, le realm et l’heure du KDC.
Petit rappel sur KRB_ERROR
Le message est décrit dans la RFC 4120, section 5.9.1, et la liste des codes se trouve en section 7.5.9. Dans Wireshark, c’est le message de type 30 (champ msg-type), qui contient les champs suivants:
- ctime / cusec: l’heure du client, seulement si elle figurait dans la requête,
- stime / susec: l’heure du KDC au moment de l’erreur, toujours présente,
- error-code: le code numérique, celui qui nous intéresse,
- crealm, cname: le client concerné,
- realm, sname: le service demandé (krbtgt/TEST.LOCAL pour un AS-REQ, le SPN pour un TGS-REQ),
- e-text et e-data: un texte libre et des données complémentaires dont le format dépend du code.
Côté Windows, le contrôleur de domaine reprend le même code dans ses événements de sécurité, en hexadécimal: champ « Result Code » du 4768 (demande de TGT), « Failure Code » du 4769 (demande de ticket de service) et du 4771 (échec de pré-authentification). Une fois le décimal converti, ce que nous lisons dans la capture se retrouve donc tel quel dans le journal du DC: 25 dans Wireshark devient 0x19 dans le 4771.
Le schéma ci dessous replace les codes que nous allons rencontrer dans l’échange.
Attention, une erreur de l’échange AP peut échapper à un filtre sur le port 88: en HTTP, elle remonte dans la réponse du serveur web, encodée dans l’en-tête WWW-Authenticate, comme nous l’avions vu dans l’article sur les entêtes HTTP WWW-Authenticate et Authorization.
Présentation de la maquette
Nom du domaine: test.local (TESTLOCAL)
Contrôleur de domaine: dc01.test.local (192.168.0.10)
Serveur IIS: srv01.test.local (192.168.0.20), site monsite.test.local, pool d’application exécuté par svc.monsite@test.local
Poste client: win11.test.local (192.168.0.50), utilisateur alice@test.local
Nous y introduisons trois défauts, un à la fois:
- le SPN HTTP/monsite.test.local, déjà porté par svc.monsite, est ajouté aussi au compte machine srv01$. Depuis Windows Server 2012 R2, le DC refuse ce doublon: pour reproduire ce que l’on trouve encore dans les vieux domaines, nous désactivons temporairement le contrôle d’unicité dans l’attribut dSHeuristics;
- le service w32time est arrêté sur win11 et son horloge avancée de dix minutes;
- une GPO limite win11 au seul RC4 (« Sécurité réseau: configurer les types de chiffrement autorisés pour Kerberos »), alors que le DC n’accepte plus que AES.
La capture se fait sur win11 avec le filtre d’affichage kerberos. Pour ne garder que les erreurs, nous filtrons sur kerberos.msg_type == 30. La colonne Info de Wireshark affiche directement le nom du code, par exemple « KRB Error: KRB5KDC_ERR_PREAUTH_REQUIRED ». Sur une capture longue, tshark sort les mêmes informations sous forme de tableau:
tshark -r win11.pcapng -Y "kerberos.msg_type == 30" -T fields ^
-e frame.time -e ip.src -e kerberos.error_code ^
-e kerberos.CNameString -e kerberos.SNameString -e kerberos.stimeLecture des erreurs, une par une
KDC_ERR_PREAUTH_REQUIRED (25 / 0x19)
Avant même d’introduire les défauts, nous lançons une ouverture de session d’alice et nous voyons apparaître un KRB_ERROR juste après le premier AS-REQ. Ce comportement est normal: le client Windows envoie une première requête sans pré-authentification, le KDC lui répond qu’elle est nécessaire et lui indique comment la construire.
Tout se trouve dans le champ e-data, qui contient un METHOD-DATA (RFC 4120, section 5.2.7). Nous y trouvons la liste des méthodes de pré-authentification acceptées: PA-ENC-TIMESTAMP, PA-PK-AS-REQ si PKINIT est possible, et surtout PA-ETYPE-INFO2. Ce dernier donne les types de chiffrement disponibles pour le compte et le sel à utiliser, ici TEST.LOCALalice. Le client dérive alors sa clé du mot de passe, chiffre un horodatage et renvoie un second AS-REQ.
-> Un 0x19 suivi d’un AS-REP n’est pas une panne. C’est le second AS-REQ qu’il faut regarder.
SPN en double: KDC_ERR_S_PRINCIPAL_UNKNOWN (7) ou PRINCIPAL_NOT_UNIQUE (8)
Avec le premier défaut en place, alice ouvre https://monsite.test.local. Le TGT est obtenu, puis le TGS-REQ pour HTTP/monsite.test.local revient en erreur, et le champ sname du KRB_ERROR nous donne le SPN demandé par le navigateur.
C:\> klist get HTTP/monsite.test.local
Current LogonId is 0:0x5a3f1
Error calling API LsaCallAuthenticationPackage (GetTicket substatus): 0x80090303
klist failed with 0x80090303/-2146893053: The specified target is unknown or unreachableMicrosoft fait le lien entre SEC_E_TARGET_UNKNOWN (0x80090303) côté client et KDC_ERR_S_PRINCIPAL_UNKNOWN (0x7) côté KDC dans la documentation de la jonction hybride Entra Kerberos. Selon les versions, le KDC Windows renvoie 0x7 ou 0x8 pour un doublon, et Microsoft traite les deux cas dans le même article de support. Pour savoir s’il s’agit d’un SPN absent ou en double, il faut regarder le journal Système du DC: un doublon y laisse un événement 11 de la source KDC, qui signale plusieurs comptes portant le même nom.
C:\> setspn -X
Checking domain DC=test,DC=local
Processing entry 0
HTTP/monsite.test.local is registered on these accounts:
CN=svc.monsite,OU=Services,DC=test,DC=local
CN=SRV01,OU=Serveurs,DC=test,DC=local
found 1 group of duplicate SPNs.Pour l’utilisateur, le symptôme est trompeur: le site s’ouvre quand même, parce que Negotiate retombe sur NTLM. C’est le scénario que nous avions déroulé dans Pourquoi Kerberos retombe en NTLM. La correction consiste à retirer le SPN du compte machine (setspn -D HTTP/monsite.test.local srv01), puis à remettre en place le contrôle d’unicité.
Horloge décalée: KRB_AP_ERR_SKEW (37 / 0x25)
Nous retirons le premier défaut et avançons l’horloge de win11. Au second AS-REQ, le KDC déchiffre l’horodatage PA-ENC-TIMESTAMP et constate qu’il sort de la tolérance. Par défaut, celle-ci est de 5 minutes (stratégie « Tolérance maximale pour la synchronisation de l’horloge de l’ordinateur »), et le KDC répond 37.
Le KRB_ERROR contient ici l’information qui permet de conclure sans autre outil: stime donne l’heure du KDC. Il suffit de la comparer à l’horodatage de la trame dans la capture, prise sur le client, pour mesurer l’écart. Sur le DC, nous trouvons un 4771 avec Failure Code 0x25. À l’ouverture de session, Windows affiche le message bien connu indiquant une différence d’heure ou de date entre le client et le serveur (STATUS_TIME_DIFFERENCE_AT_DC, 0xC0000133).
Attention: le même code peut aussi revenir à l’échange AP, lorsque c’est l’horloge du serveur applicatif qui dérive. Dans ce cas, rien ne passe par le port 88, et c’est le journal du serveur qu’il faut lire.
Type de chiffrement refusé: KDC_ERR_ETYPE_NOSUPP (14 / 0xE)
Avec la GPO RC4 appliquée sur win11, l’AS-REQ ne propose plus que l’etype 23 (rc4-hmac). Le KDC n’en accepte plus et répond 14. Dans Wireshark, nous dépilons le champ etype de la requête (KDC-REQ-BODY) pour voir ce que le client a proposé, puis nous le comparons au PA-ETYPE-INFO2 d’un échange réussi sur un autre poste, qui montre ce que le compte possède. Sur le DC, l’événement 4768 porte le Result Code 0xE.
Le même code apparaît dans un cas plus discret: un compte dont l’attribut msDS-SupportedEncryptionTypes annonce AES alors que son mot de passe n’a pas été changé depuis le passage au niveau fonctionnel 2008. Ce compte n’a donc aucune clé AES. Pour générer les clés AES manquantes, il faut changer le mot de passe du compte.
| Code | Nom | Échange | Cause côté KDC | Trace sur le DC |
|---|---|---|---|---|
| 25 / 0x19 | KDC_ERR_PREAUTH_REQUIRED | AS | Normal, pré-authentification demandée | aucune |
| 6 / 0x6 | KDC_ERR_C_PRINCIPAL_UNKNOWN | AS | Compte inexistant | 4768, 0x6 |
| 24 / 0x18 | KDC_ERR_PREAUTH_FAILED | AS | Mauvais mot de passe | 4771, 0x18 |
| 7 / 0x7 | KDC_ERR_S_PRINCIPAL_UNKNOWN | TGS | SPN absent (ou en double) | 4769, événement KDC 11 si doublon |
| 37 / 0x25 | KRB_AP_ERR_SKEW | AS, TGS, AP | Horloge hors tolérance | 4771, 0x25 |
| 14 / 0xE | KDC_ERR_ETYPE_NOSUPP | AS, TGS | Aucun etype commun | 4768 ou 4769, 0xE |
Ce que ces erreurs disent à un attaquant
Le KDC fournit ces mêmes codes à n’importe quelle machine qui joint le port 88, sans authentification préalable.
Le premier usage est l’énumération de comptes. Un AS-REQ sans pré-authentification pour un nom inexistant reçoit un 6, et pour un nom existant il reçoit un 25. Une simple liste de noms suffit donc à trier les comptes valides. Ces tentatives n’incrémentent pas badPwdCount et ne génèrent aucun 4771: seul un 4768 en échec avec 0x6 en garde la trace, à condition que l’audit des échecs du « Service d’authentification Kerberos » soit activé sur les DC. Cet audit est souvent absent.
Une fois la liste obtenue, l’attaquant passe à la pulvérisation de mots de passe (MITRE ATT&CK T1110.003): un ou deux mots de passe courants essayés sur tous les comptes, en restant sous le seuil de verrouillage. Chaque essai raté produit un 24 et un 4771 avec 0x18. Ce qui doit alerter, c’est la corrélation: une même adresse source et des dizaines de comptes différents en 0x18 sur une courte période, ou un 0x18 suivi d’un 4768 réussi pour le même compte depuis la même source.
L’absence d’erreur est aussi un signal. Un compte dont le userAccountControl porte DONT_REQ_PREAUTH (0x400000) reçoit directement un AS-REP, sans passer par le 25. Or une partie de cet AS-REP est chiffrée avec la clé dérivée de son mot de passe et peut être attaquée hors ligne: c’est l’AS-REP Roasting (T1558.004). Dans une capture, nous le reconnaissons à un AS-REP qui arrive sans KRB_ERROR 25 avant lui. Sur le DC, il apparaît comme un 4768 avec « Pre-Authentication Type » à 0. Pour inventorier ces comptes:
Get-ADUser -Filter 'DoesNotRequirePreAuth -eq $true' -Properties DoesNotRequirePreAuth |
Select-Object SamAccountName, EnabledSi elle renvoie des comptes, il faut retirer l’option, ou à défaut leur imposer un mot de passe long et aléatoire.
Le type de chiffrement joue le même rôle côté service. svc.monsite porte un SPN: n’importe quel utilisateur du domaine peut demander un ticket pour ce service et tenter de casser hors ligne la partie chiffrée avec la clé du compte (Kerberoasting, T1558.003). Si ce compte accepte encore RC4, l’attaquant demandera l’etype 23, beaucoup plus rapide à attaquer. Le signal à surveiller est un 4769 avec « Ticket Encryption Type » à 0x17 sur un domaine censé être passé en AES. C’est pour cela que le 0xE du troisième défaut est une bonne nouvelle quand il frappe un vieux poste: il montre que le refus de RC4 est réellement appliqué. Le type de chiffrement d’un ticket se lit avec klist, comme dans l’article Kerberos: lire un ticket de service avec klist et Wireshark.
Le KDC chiffrant le ticket avec la clé du compte qui porte le SPN, un SPN posé par erreur sur un compte au mot de passe faible l’expose à la même attaque, et un droit d’écriture sur servicePrincipalName permet d’en créer un sur n’importe quel compte. setspn -X et la revue des ACL sur cet attribut ont leur place dans un audit régulier.
Pour la détection, le minimum est d’activer sur tous les DC les sous-catégories d’audit « Service d’authentification Kerberos » et « Opérations de ticket du service Kerberos », en succès comme en échec, puis de remonter les 4768, 4769 et 4771 vers le SIEM avec les champs Result Code, Failure Code, Ticket Encryption Type et l’adresse du client. Les codes 0x6, 0x18, 0x19 n’ont de sens que comptés par source et par fenêtre de temps, un événement isolé ne dit presque rien.
Rémi Vernier
Responsable des opérations cybersécurité dans un grand groupe international. Identité, Active Directory, Entra ID, cloud hybride, et désormais SAP, IA et modèles en local.