CVE-2025-33073 – NTML dans la sauce

Introduction

Depuis près de deux décennies, Windows est affecté par des vulnérabilités de réflexion NTLM. Dans cet article, nous présentons CVE-2025-33073, une vulnérabilité logique qui contourne les mesures d’atténuation de la réflexion NTLM et permet à un attaquant distant authentifié d’exécuter des commandes arbitraires en tant que SYSTEM sur toute machine qui n’impose pas la signature SMB. La découverte de la vulnérabilité, l’analyse complète de la cause racine ainsi que le correctif de Microsoft seront détaillés dans cet article. Merci au équipe de Synactiv pour leurs travail.

Comprendre la Réflexion NTLM

La réflexion NTLM est un cas particulier de relais d’authentification NTLM dans lequel l’authentification originale est relayée vers la machine à partir de laquelle l’authentification a été initiée. Cette classe de vulnérabilité a été introduite publiquement via MS08-68, où Microsoft a empêché la réflexion NTLM de SMB à SMB. Au fil des ans, d’autres vecteurs d’exploitation ont été découverts et corrigés, tels que la réflexion HTTP vers SMB (corrigée dans MS09-13) ou la réflexion DCOM vers DCOM (corrigée dans MS15-076).

Découverte de la Vulnérabilité

Pour nos tests de base, voyons ce qui se passe lorsque nous essayons de relayer une authentification SMB vers la même machine. Notre machine de test (SRV1) est un Windows Server 2022 à jour, joignable au domaine, avec la signature SMB non imposée :

$ PetitPotam.py -u loki -p loki -d ASGARD.LOCAL 192.168.56.3 SRV1.ASGARD.LOCAL
[-] Envoi de EfsRpcEncryptFileSrv !
[+] Exception ERROR_BAD_NETPATH attendue reçue !!
[+] L'attaque a fonctionné !

# ntlmrelayx.py -t SRV1.ASGARD.LOCAL -smb2support
[*] Serveurs démarrés, en attente de connexions
[*] SMBD-Thread-5 (process_request_thread) : Connexion reçue de 192.168.56.14, attaque de la cible smb://SRV1.ASGARD.LOCAL
[-] Échec de l'authentification contre smb://SRV1.ASGARD.LOCAL en tant que ASGARD/SRV1$

PetitPotam force un service SYSTEM (lsass.exe) à s’authentifier auprès d’une machine contrôlée, donc une authentification de compte machine est reçue. Comme l’authentification provient de la même machine, le relais échoue.

Comprendre la Vulnérabilité

Afin de comprendre rapidement ce qui s’est passé, des captures réseau ont été effectuées pour les deux attaques de relais. Une différence évidente est apparue : dans la capture réseau du relais avec le nom d’hôte srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA, une authentification locale NTLM a eu lieu ! En revanche, lorsque la machine est forcée avec une adresse IP comme écouteur, une authentification NTLM standard s’est produite.

Authentification Locale NTLM

L’authentification locale NTLM est un cas particulier d’authentification NTLM dans lequel le serveur informe le client (dans le message NTLM_CHALLENGE) qu’il n’est pas nécessaire de calculer la réponse au défi dans le message NTLM_AUTHENTICATE. Au lieu de cela, le serveur définit le « Negotiate Local Call » dans le message de défi, crée un contexte serveur, l’ajoute à une liste de contextes globale et insère l’ID de contexte dans le champ Réservé. Lorsque le client reçoit le message NTLM_CHALLENGE, il comprend que l’authentification locale NTLM doit avoir lieu. Il ajoute ensuite son jeton au contexte serveur qui a été passé via l’ID dans le champ Réservé. Comme le client et le serveur sont sur la même machine, tout se passe dans le même processus lsass.exe. Finalement, le client renvoie un message NTLM_AUTHENTICATE qui est presque vide et le serveur utilise le jeton ajouté à son contexte pour effectuer des opérations ultérieures (via SMB dans notre cas).

Cause Racine

Pour comprendre la cause racine de la vulnérabilité, nous avons remonté à l’initialisation du contexte d’authentification par le client SMB (mrxsmb.sys). Lorsqu’il détecte qu’une authentification doit être effectuée, il appelle la fonction ksecdd!AcquireCredentialsHandle (qui effectue un appel RPC à LSASS vers la fonction équivalente en mode utilisateur) avec le package Negotiate pour récupérer un handle de credential avec l’identité de l’utilisateur actuel. Ensuite, le client appelle ksecdd!InitializeSecurityContextW, qui est également un appel RPC à LSASS. Selon que la coercition d’authentification a été effectuée avec une adresse IP ou l’enregistrement DNS, le nom de la cible passé à InitializeSecurityContextW peut ressembler à :

  • cifs/192.168.56.3
  • cifs/srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA

Le point d’entrée en mode utilisateur pour cette fonction est lsasrv!SspiExProcessSecurityContext. Cette fonction appelle lsasrv!LsapCheckMarshalledTargetInfo pour supprimer les informations de cible marshalées qui peuvent être présentes dans le nom de la cible :

NTSTATUS LsapCheckMarshalledTargetInfo(UNICODE_STRING *TargetName)
{
[...]
    status = CredUnmarshalTargetInfo(TargetName->Buffer, TargetName->Length, 0, &TargetInfoSize);
    if (NT_SUCESS(status))
    {
        Length = TargetName->Length;
        TargetName->MaximumLength = TargetName->Length;
        TargetName->Length = Length - TargetInfoSize;
    }
[...]
    return status;
}

Après l’appel de cette fonction, le nom de la cible ressemble maintenant à :

  • cifs/192.168.56.3
  • cifs/srv1

Plus tard, LSASS appelle le package d’authentification qui a été négocié (NTLM dans notre cas), plus spécifiquement, la fonction msv1_0!SpInitLsaModeContext. Comme un message NTLM_NEGOTIATE doit être créé, msv1_0!SsprHandleFirstCall est appelé. À l’intérieur de cette fonction, plusieurs vérifications sont effectuées pour décider si le nom de la station de travail et le nom de domaine doivent être inclus dans le message NTLM_NEGOTIATE :

NTSTATUS SsprHandleFirstCall(
        HANDLE CredentialHandle,
        NTLM_SSP_CONTEXT **SspContext,
        ULONG fContextReq,
        int a4,
        PSSP_CREDENTIAL Credential,
        UNICODE_STRING *TargetName,
        _DWORD *a7,
        void **a8,
        LARGE_INTEGER SystemTime,
        LARGE_INTEGER *a10,
        _OWORD *a11,
        LARGE_INTEGER LocalTime)
{
    SspCredentialReferenceCredentialEx(CredentialHandle, 0, 1, &Credential);
[...]
    SspIsTargetLocalhost(1, TargetName, &SspContext->IsLoopbackAllowed);
[...]
    if (!SspContext->IsLoopbackAllowed && !NtLmGlobalDisableLoopbackCheck
        || (fContextReq & ISC_REQ_NULL_SESSION) != 0
        || Credential->DomainName
        || Credential->UserName
        || Credential->Password) {
        SspContext->CheckForLocal = FALSE;
    } else {
        SspContext->CheckForLocal = TRUE;
    }
[...]
    if (SspContext->CheckForLocal) {
        RtlCopyAnsiString(WorkstationName, NtLmGlobalOemPhysicalComputerNameString);
        RtlCopyAnsiString(DomainName, NtLmGlobalOemPrimaryDomainNameString);
        NegotiateMessage->OemWorkstationName =  WorkstationName;
        NegotiateMessage->OemDomainName =  DomainName;
    }
[...]
}

Tout d’abord, la fonction msv1_0!SspIsTargetLocalhost est utilisée pour déterminer si le nom de la cible correspond à la machine actuelle. Pour ce faire, la partie après la classe de service (192.168.56.3 ou srv1) est comparée (sans tenir compte de la casse) à plusieurs chaînes :

  • Le FQDN de la machine (SRV1.ASGARD.LOCAL)
  • Le nom d’hôte de la machine (SRV1) → dans notre cas, il correspond !
  • localhost

S’il n’y a pas de correspondance, le nom de la cible est considéré comme une adresse IP, et est comparé à toutes les adresses IP assignées à la machine actuelle. Si aucune des vérifications précédentes ne passe, alors le nom de la cible est considéré comme différent de la machine actuelle.

Enfin, le nom de la station de travail et le nom de domaine sont inclus dans le message NTLM_NEGOTIATE si toutes les conditions suivantes sont remplies :

  • La cible est la machine actuelle
  • Le client n’a pas demandé d’authentification NULL
  • Les informations d’identification de l’utilisateur actuel sont utilisées (aucune information d’identification explicite n’a été spécifiée)

Dans notre cas, toutes ces conditions sont vraies, c’est pourquoi le client SMB indique au serveur de procéder à une authentification locale NTLM lors de la coercition avec le nom srv11UWhRCAAAAAAAAAAAAAAAAAAAAAAAAAAAAwbEAYBAAAA.

La dernière question est : pourquoi sommes-nous privilégiés sur la machine ? Eh bien, PetitPotam force lsass.exe à s’authentifier auprès de notre serveur et lsass.exe s’exécute en tant que SYSTEM. Lorsque le client (lsass.exe) reçoit le message NTLM_CHALLENGE indiquant que l’authentification locale NTLM doit être effectuée, il copie son jeton SYSTEM dans le contexte du serveur. Lorsque le serveur reçoit le message NTLM_AUTHENTICATE, il récupère le jeton de l’objet contexte et l’utilise pour effectuer des actions ultérieures via SMB (dans notre cas, utiliser le service Remote Registry pour dumper la ruche SAM et compromettre la machine).

Analyse du Correctif et Recommandations

Microsoft a décrit CVE-2025-33073 comme une vulnérabilité dans le client SMB. Par conséquent, pour comprendre le correctif, le pilote du noyau mrxsmb.sys a été comparé avec celui d’avant le correctif. La comparaison a révélé que seulement quelques fonctions ont été modifiées. La fonction intéressante est mrxsmb!SmbCeCreateSrvCall, qui est appelée lors de la tentative d’accès à une ressource via SMB. Le code suivant a été ajouté :

NTSTATUS SmbCeCreateSrvCall([...])
{
[...]
    if ((unsigned int)CredUnmarshalTargetInfo(TargetName->Buffer, TargetName->Length, 0, 0) != STATUS_INVALID_PARAMETER ) {
        return STATUS_INVALID_PARAMETER;
    }
[...]
}

La fonction ksecdd!CredUnmarshalTargetInfo échoue si le nom de la cible ne contient aucune information de cible marshalée, ou si le format est incorrect. Par conséquent, cet appel a été ajouté pour empêcher toute connexion SMB si l’utilisation d’un nom de cible avec des informations de cible marshalées est détectée. Par conséquent, ce correctif empêche l’exploitation de la vulnérabilité en supprimant la capacité de forcer les machines à s’authentifier via Kerberos en enregistrant un enregistrement DNS avec des informations de cible marshalées. Cependant, il peut encore être exploitable en trouvant une alternative à la technique d’enregistrement DNS pour forcer un client à s’authentifier auprès de notre serveur de relais.

Pour corriger correctement la vulnérabilité, veuillez vous référer à l’avis officiel de Microsoft. De plus, pour prévenir toute future vulnérabilité liée au relais d’authentification sur SMB, imposez la signature SMB sur vos machines lorsque cela est possible. Dans ce contexte, l’imposition de la signature SMB empêche l’exploitation de cette vulnérabilité, même sans appliquer le correctif.

Conclusion

Bien que CVE-2025-33073 soit référencée par Microsoft comme une élévation de privilèges, il s’agit en réalité d’une exécution de commande à distance authentifiée en tant que SYSTEM sur toute machine qui n’impose pas la signature SMB.

Dans cet article de blog, nous avons décrit comment nous avons accidentellement découvert la vulnérabilité, détaillé notre méthodologie pour obtenir rapidement un aperçu des spécificités de la vulnérabilité et plongé profondément dans les internes de LSASS pour obtenir une compréhension complète du flux de travail de la vulnérabilité. Enfin, nous avons analysé le correctif officiel pour illustrer que la vulnérabilité a été corrigée avec seulement quelques lignes de code.

En dernier lieu, nous voulions souligner que CVE-2025-33073 est un bon exemple de la raison pour laquelle l’activation des mesures de défense en profondeur telles que la signature SMB peut s’avérer extrêmement efficace, même contre les vulnérabilités zero-day. Aussi, félicitations aux chercheurs qui ont rapporté indépendamment la vulnérabilité à Microsoft !

Cyberfishement 🐡