Aller au contenu

Accueil / Sécurité et identité

Silver ticket: forger un ticket de service sur maquette

Ce que contient un ticket de service, pourquoi la clé du compte de service suffit à le forger, et les traces qui restent sur le serveur quand le KDC ne voit rien.

Rémi Vernier
11 novembre 2026 · 9 min de lecture

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:

code
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.local

Pour 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:

shell
ktpass /princ HTTP/monsite.test.local@TEST.LOCAL /mapuser TESTLOCAL\svc.monsite ^
       /pass * /crypto All /ptype KRB5_NT_PRINCIPAL /out C:\temp\monsite.keytab

Attention: 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:

code
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é krbtgt

Tout 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:

  1. le nom du domaine et son SID, qui ne sont pas des secrets (n’importe quel utilisateur les lit dans l’annuaire),
  2. le SPN visé, ici HTTP/monsite.test.local,
  3. 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,
  4. un nom d’utilisateur et un RID, existants ou non,
  5. la liste des groupes à placer dans PAC_LOGON_INFO, par exemple 512 (Admins du domaine),
  6. 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.

dc01 (KDC) événements 4768 / 4769 srv01 (IIS) HTTP/monsite.test.local événements 4624 / 4627 cli01 (alice) flux légitime Poste attaquant détient la clé Ks 1-2. AS-REQ, TGS-REQ AS-REP, TGS-REP 3. AP-REQ (ticket émis par le KDC) AP-REQ avec ticket forgé, aucun échange avec le KDC validation du PAC (optionnelle)
Flux légitime et silver ticket: le KDC n’intervient que dans le premier cas, sauf si le serveur demande la validation du PAC.

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:

code
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 SupportedEncryptionTypes

Une 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:

code
auditpol /set /subcategory:"Logon" /success:enable /failure:enable
auditpol /set /subcategory:"Special Logon" /success:enable
auditpol /set /subcategory:"Group Membership" /success:enable

Sur 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:

code
$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

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.

Tous les articles → LinkedIn →

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *