Un 4624 sans 4769
Sur srv01, le journal Sécurité affiche un événement 4624 tout à fait banal: ouverture de session de type 3, package d’authentification Kerberos, compte TESTLOCAL\alice, adresse source 192.168.0.110. Sur les contrôleurs de domaine, nous ne trouvons pourtant aucun événement 4769 correspondant à une demande de ticket pour HTTP/monsite.test.local dans ce créneau, et l’événement 4627 du serveur attribue à alice une appartenance au groupe « Admins du domaine » qu’elle n’a jamais eue dans l’annuaire.
Ce décalage entre ce que le serveur a accepté et ce que le KDC a émis est la signature d’un silver ticket, référencé chez MITRE sous la technique T1558.002: un ticket de service fabriqué de toutes pièces avec la clé du compte qui porte le SPN, sans jamais parler au KDC.
La maquette reprend celle de l’article sur la configuration de IIS pour Kerberos, remise au goût du jour:
- Domaine: test.local (TESTLOCAL), contrôleur de domaine dc01
- Serveur IIS: srv01, site monsite.test.local, pool d’application exécuté par svc.monsite@test.local qui porte le SPN HTTP/monsite.test.local
- Client: cli01, utilisateur alice@test.local
- Outils: klist, Wireshark, ktpass et PowerShell
Je voulais donner des exemples et des commandes à dérouler sur la maquette.
Tout ce qui suit se rejoue donc sur test.local: l’observation d’un ticket légitime, le déchiffrement de son PAC, la lecture des journaux et le durcissement. Seule la ligne de commande de forge n’est pas reproduite ici. Nous la remplaçons par la liste de ce qu’elle consomme, ce qui suffit pour comprendre le risque.
Ce que contient un ticket de service
Commençons par un ticket légitime. Depuis cli01, nous vidons le cache, ouvrons le site puis listons les tickets:
C:\> klist purge
C:\> start msedge https://monsite.test.local
C:\> klist
#1> Client: alice @ TEST.LOCAL
Server: HTTP/monsite.test.local @ TEST.LOCAL
KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
Ticket Flags 0x40a10000 -> forwardable renewable pre_authent name_canonicalize
Start Time: 11/11/2026 9:12:04 (local)
End Time: 11/11/2026 19:12:04 (local)
Renew Time: 11/18/2026 9:12:04 (local)
Session Key Type: AES-256-CTS-HMAC-SHA1-96
Cache Flags: 0
Kdc Called: dc01.test.localPour voir l’intérieur du ticket, Wireshark a besoin de la clé du service. Comme nous l’avions fait dans l’article sur le déchiffrement du PAC, nous générons un keytab pour svc.monsite:
ktpass /princ HTTP/monsite.test.local@TEST.LOCAL /mapuser TESTLOCAL\svc.monsite ^
/pass * /crypto All /ptype KRB5_NT_PRINCIPAL /out C:\temp\monsite.keytabAttention: ktpass applique au compte le mot de passe saisi. Sur la maquette, saisissez le mot de passe actuel de svc.monsite, faute de quoi le pool d’application ne démarrera plus. Ce fichier contient la clé dont un attaquant a besoin pour forger des tickets: supprimez-le une fois l’analyse terminée.
Dans Wireshark, le keytab se déclare dans « Edit » / « Preferences » / « Protocols » / « KRB5 », en cochant « Try to decrypt Kerberos blobs ». Nous capturons ensuite sur le client avec le filtre kerberos || http, puis nous ouvrons le KRB_AP_REQ contenu dans l’en-tête « Authorization » (celui qui commence par YII). La structure se lit ainsi:
Ticket
realm: TEST.LOCAL (en clair)
sname: HTTP/monsite.test.local (en clair, hors partie chiffrée)
enc-part: etype 18 (aes256-cts-hmac-sha1-96) -> chiffré avec Ks
flags, clé de session, crealm, cname: alice
authtime, starttime, endtime, renew-till
authorization-data
AD-IF-RELEVANT
AD-WIN2K-PAC
PAC_LOGON_INFO (1) SID du domaine, RID, groupes, SID extra
PAC_CLIENT_INFO (10) nom du client et authtime
PAC_UPN_DNS_INFO (12)
PAC_ATTRIBUTES_INFO (17), PAC_REQUESTOR (18)
PAC_SERVER_CHECKSUM (6) signé avec Ks
PAC_PRIVSVR_CHECKSUM (7) signé avec la clé krbtgt
PAC_TICKET_CHECKSUM (16) signé avec la clé krbtgt
PAC_FULL_CHECKSUM (19) signé avec la clé krbtgtTout ce qui décide de l’autorisation sur srv01 (identité, groupes, durée de vie) se trouve dans la partie chiffrée avec Ks, la clé dérivée du mot de passe de svc.monsite. Le détail de la lecture d’un ticket et de la validation du PAC est repris dans l’article du KRB_TGS_REQ au PAC validé.
Pourquoi la clé du service suffit
Quand srv01 reçoit le KRB_AP_REQ, LSASS déchiffre le ticket avec Ks, vérifie l’authenticator avec la clé de session trouvée dans le ticket, contrôle les dates, puis vérifie la signature serveur du PAC, elle aussi calculée avec Ks. Si tout est cohérent, le jeton d’accès est construit à partir des SID du PAC. À aucun moment le serveur n’a besoin de contacter dc01.
Celui qui détient Ks peut donc fabriquer lui-même un ticket que srv01 jugera parfaitement valide. Les ingrédients sont peu nombreux:
- le nom du domaine et son SID, qui ne sont pas des secrets (n’importe quel utilisateur les lit dans l’annuaire),
- le SPN visé, ici HTTP/monsite.test.local,
- la clé Ks: l’empreinte NT du compte pour un ticket en RC4, ou la clé AES dérivée du mot de passe et du sel,
- un nom d’utilisateur et un RID, existants ou non,
- la liste des groupes à placer dans PAC_LOGON_INFO, par exemple 512 (Admins du domaine),
- des dates de validité au choix du forgeur.
Restent les trois signatures calculées avec la clé krbtgt: srv01 ne connaît pas cette clé et ne peut pas les vérifier seul, le forgeur y place donc n’importe quelle valeur de la bonne taille. Ces signatures ont été ajoutées pour d’autres attaques, CVE-2020-17049 (Bronze Bit) pour la signature du ticket et CVE-2022-37967 pour la signature complète du PAC. Elles protègent les tickets qui repassent par le KDC, en S4U2proxy par exemple, pas ceux que le service accepte directement.
Pour obtenir Ks, l’attaquant a plusieurs chemins classiques. Un compte de service utilisateur avec un SPN et un mot de passe humain se casse hors ligne après un simple Kerberoasting (T1558.003), puisque n’importe quel utilisateur du domaine peut demander un ticket pour ce SPN. Un administrateur local de srv01 peut extraire de LSA le secret du compte machine ou celui du compte qui exécute le pool. Enfin, un attaquant qui dispose déjà des droits de réplication lit toutes les clés de l’annuaire.
Le rayon d’impact dépend du compte. Avec la clé de svc.monsite, l’attaquant se présente au site comme n’importe qui, avec n’importe quels groupes: si l’application donne des droits à un groupe, il lui suffit de l’ajouter à son PAC. Avec la clé du compte machine srv01$, c’est plus grave. Le nom du service (sname) se trouve hors de la partie chiffrée, donc un même ticket se présente indifféremment à CIFS, HOST, HTTP ou WSMAN dès lors que ces services partagent la clé du compte machine. L’attaquant obtient alors l’administration complète du serveur. La propagation reste toutefois bornée aux services qui acceptent cette clé: là où un golden ticket ouvre aussi srv02, un silver ticket forgé avec la clé de srv01$ s’arrête à srv01.
Pour la révocation, il n’y a rien à révoquer: le ticket n’existe dans aucune base, et sa durée de vie est celle qu’a choisie le forgeur. La seule parade consiste à changer la clé. Attention: pour un compte machine, le serveur conserve aussi le mot de passe précédent afin de ne pas casser les tickets en cours, une seule rotation ne suffit donc pas à invalider l’ancienne clé.
Validation du PAC et type de chiffrement
Le service a pourtant un moyen de faire contrôler le PAC: l’envoyer au contrôleur de domaine par Netlogon (message KERB_VERIFY_PAC, décrit dans [MS-APDS]), qui vérifie la signature KDC avec la clé krbtgt. Un PAC forgé échoue à ce contrôle. Malheureusement, Windows ne déclenche pas cette vérification pour les services qui s’exécutent avec le privilège « Agir en tant que partie du système d’exploitation », ce qui est le cas de tout ce qui tourne en Local System, à commencer par le serveur SMB. La valeur ValidateKdcPacSignature sous HKLM\SYSTEM\CurrentControlSet\Control\Lsa\Kerberos\Parameters permet de forcer ce comportement. Ses effets exacts ont toutefois évolué au fil des mises à jour de durcissement du PAC: testez-la sur la maquette avant de compter dessus, en gardant en tête qu’elle ajoute un aller-retour vers un DC à chaque ouverture de session.
Le type de chiffrement mérite aussi un contrôle. L’attribut msDS-SupportedEncryptionTypes du compte guide le KDC quand il émet un ticket, mais le forgeur, lui, ne passe pas par le KDC. Ce qui compte, c’est ce que srv01 accepte, et cela dépend de sa stratégie locale « Sécurité réseau: configurer les types de chiffrement autorisés pour Kerberos ». Tant que RC4 y reste autorisé, l’empreinte NT seule suffit au forgeur. Vérifions les deux côtés:
PS> Get-ADUser svc.monsite -Properties servicePrincipalName,msDS-SupportedEncryptionTypes,PasswordLastSet |
Select-Object servicePrincipalName,msDS-SupportedEncryptionTypes,PasswordLastSet
PS> reg query HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\Kerberos\Parameters /v SupportedEncryptionTypesUne valeur 0x18 (24) côté compte correspond à AES128 + AES256. Si la stratégie locale de srv01 exclut aussi RC4, un ticket en etype 23 devrait être refusé à l’AP-REQ. Rejouez le test sur la maquette après le déploiement de la GPO: un refus apparaît dans la capture sous forme de KRB_ERROR, que nous avions détaillé dans l’article sur la lecture des KRB_ERROR. Attention: retirer RC4 relève la barre sans fermer la porte, puisqu’un attaquant qui a extrait la clé AES forge tout aussi bien.
Ce qui reste côté serveur
Puisque dc01 ne voit rien, la détection se joue sur srv01. Il faut d’abord que l’audit y soit actif:
auditpol /set /subcategory:"Logon" /success:enable /failure:enable
auditpol /set /subcategory:"Special Logon" /success:enable
auditpol /set /subcategory:"Group Membership" /success:enableSur les contrôleurs de domaine, l’audit « Kerberos Service Ticket Operations » produit les 4769 qui serviront à la corrélation. L’événement 4624 de srv01 porte un champ « GUID d’ouverture de session ». Pour un ticket légitime, nous retrouvons ce même GUID dans le 4769 émis par le DC qui a délivré le ticket. Pour un ticket forgé, aucun 4769 ne lui correspond. Voici de quoi faire le rapprochement sur la maquette:
$debut = (Get-Date).AddHours(-12)
$ouvertures = Get-WinEvent -ComputerName srv01 -FilterHashtable @{LogName='Security'; Id=4624; StartTime=$debut} |
ForEach-Object {
$d = @{}; ([xml]$_.ToXml()).Event.EventData.Data | ForEach-Object { $d[$_.Name] = $_.'#text' }
if ($d.AuthenticationPackageName -eq 'Kerberos' -and $d.LogonType -eq '3') {
[pscustomobject]@{ Heure=$_.TimeCreated; Compte=$d.TargetUserName; Domaine=$d.TargetDomainName; Guid=$d.LogonGuid; Ip=$d.IpAddress }
}
}
$guidsKdc = Get-ADDomainController -Filter * | ForEach-Object {
Get-WinEvent -ComputerName $_.HostName -FilterHashtable @{LogName='Security'; Id=4769; StartTime=$debut.AddHours(-10)} |
ForEach-Object { (([xml]$_.ToXml()).Event.EventData.Data | Where-Object Name -eq 'LogonGuid').'#text' }
}
$ouvertures | Where-Object { $_.Guid -notin $guidsKdc }Attention: ce rapprochement donne des faux positifs (ticket émis avant la fenêtre interrogée, journaux d’un DC purgés, GUID nul sur certains événements), il sert à trier, pas à conclure. Il faut interroger tous les DC, et remonter au moins sur la durée de vie d’un ticket.
D’autres signaux viennent compléter ce tri:
- le 4627 liste les groupes tirés du PAC: un groupe privilégié pour un compte qui n’en est pas membre dans l’annuaire, ou un SID qui n’existe pas, ne s’explique pas autrement,
- un 4672 (privilèges spéciaux) pour un compte ordinaire sur un serveur applicatif,
- un champ « Domaine du compte » dont la forme diffère des ouvertures habituelles (FQDN au lieu du nom NetBIOS, casse inhabituelle), ce qui arrive avec certains outils de forge,
- un compte qui n’existe plus ou n’a jamais existé.
Sur le poste, quand le ticket a été injecté dans le cache, klist le montre avec une durée de vie qui sort de la stratégie du domaine (10 heures par défaut) et souvent un champ « Kdc Called » vide. En capture réseau, nous voyons un KRB_AP_REQ vers srv01 sans aucun KRB_TGS_REQ préalable de cette machine vers un DC. Avec le keytab chargé dans Wireshark, l’etype du ticket et les signatures KDC incohérentes apparaissent en clair.
Ces signaux restent difficiles à exploiter à grande échelle, car l’ouverture de session forgée a toute l’apparence d’une ouverture normale. Le levier le plus efficace reste de rendre Ks inutilisable ou introuvable. Pour les comptes de service, un gMSA apporte un mot de passe aléatoire de 240 octets renouvelé tous les 30 jours par défaut, ce qui ferme le Kerberoasting et limite la durée de vie d’une clé volée. Nous vérifions aussi que les comptes machine renouvellent bien leur mot de passe (pas de DisablePasswordChange positionné). Ensuite, nous retirons RC4 des comptes et des serveurs, nous limitons l’administration locale de srv01 puisqu’elle donne accès aux secrets LSA, et nous ne laissons traîner aucun keytab. Après une compromission avérée, nous changeons le mot de passe de svc.monsite puis redémarrons le pool, et nous faisons tourner deux fois le mot de passe du compte machine.
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.