
Exploit distant du système de fichiers réseau Windows pour CVE-2022-30136
auteur : Ricardo Narvaja
À des fins de démonstration uniquement. L'exploit complet fonctionne sur les systèmes Windows Server vulnérables.
Consultez l'article détaillé Analyse de CVE-2022-30136 « Vulnérabilité du système de fichiers réseau Windows ».
J'ai voulu rédiger cet article pour démontrer l'analyse que j'ai effectuée lors du développement de l'exploit Core Impact « Windows Network File System Remote » qui exploite la vulnérabilité CVE-2022-30136.
La vulnérabilité d'exécution de code à distance dans le système de fichiers réseau Windows est une erreur de calcul de taille qui se produit lors de la création de la réponse du serveur dans une REQUÊTE COMPOSÉE utilisant la version 4.1 de NFS.
Le serveur calcule une taille plus petite que nécessaire pour allouer le pool, puis, lors de la copie des données pour générer la réponse, provoque un débordement du tampon.
La fonction Nfs4SvrXdrpGetEncodeOperationResultByteCount dans nfssvr.sys est appelée pour chaque opération et renvoie une taille plus petite que nécessaire (4 octets de moins pour chaque opération).
Un correctif a été appliqué pour Nfs4SvrXdrpGetEncodeOperationResultByteCount.
Cette fonction est appelée lors de chaque OPÉRATION d'une REQUÊTE COMPOSÉE afin de renvoyer les octets nécessaires pour chacune d'elles en fonction de l'OPCODE. Elle est ensuite ajoutée à l'en-tête et aux autres parties de la réponse. Ensuite, elle calcule la taille finale de l'ensemble de la réponse à allouer, puis copie dessus pour répondre.
Dans chaque cas, nous pouvons voir que la valeur de la taille renvoyée pour chaque opération est de quatre octets inférieure dans la version vulnérable par rapport à la version corrigée.
J'ai construit le POC pour Windows Server 2019.
Ci-dessous se trouve la version vulnérable de nfssvr.sys utilisée pour ce POC, suivie de la version corrigée pour Windows Server 2019 :

L'image suivante montre le CAS 26 dans la différence :

Dans l'exemple du CAS 26, nous pouvons voir que la constante ajoutée à la valeur calculée est 0x2c dans la version vulnérable, et 0x30 dans la version corrigée.
On observe la même chose dans chaque cas correspondant à chaque OPCODE. La version vulnérable renvoie toujours une taille de quatre octets inférieure à celle de la version corrigée.
Nous n'allons pas montrer tous les cas car le correctif est similaire pour tous les OPCODES.
Le parent de Nfs4SvrXdrpGetEncodeOperationResultByteCount est Nfs4SvrXdrEncodeCompoundResults. Il lit le nombre d'opérations envoyées dans la REQUÊTE COMPOSÉE.
Dans ce POC, la valeur est 0x34 (52d). Lorsque mon POC se connecte au serveur sur le port 2049 (le port par défaut pour NFS), je dois placer un point d'arrêt conditionnel pour un arrêt.


Dans cet exemple, il s'arrête lorsque number_of_operations=0x34.

Le pool avec le tag ARGS est alloué ici.

Je vais ensuite créer une structure nommée TAG_ARGS_0x10e0 pour inverser les champs.

Il copie number_of_operations dans r13 et boucle dans la fonction vulnérable une fois par opération, jusqu'à ce que le compteur atteigne la valeur de r13.

Cela montre que le premier package_OPCODE = 0x35, qui correspond à SEQUENCE dans la première opération obligatoire d'une REQUÊTE COMPOSÉE. Dans l'image ci-dessous, la flèche pointe vers cet OPCODE dans mon paquet.

Ici, nous pouvons voir les arguments de la fonction vulnérable.


À l'intérieur de la fonction vulnérable, elle lit l'OPCODE et se rend au CAS correspondant.

Trois est soustrait de la valeur d'OPCODE d'origine (53).

Et saute au CAS 50, renvoyant 0x28 comme taille nécessaire pour cette opération.


Nous pouvons voir dans la différence comment la version corrigée renvoie 0x2c.

Cette valeur renvoyée est ajoutée à la valeur précédente des autres champs de la réponse afin de calculer la taille des opérations. Dans ce cas, cette valeur est 0x40c.

Ci-dessous, nous pouvons voir les valeurs en cours d'ajout :

Lorsqu'elle sort de la boucle, la taille totale est calculée. Dans ce cas, la taille totale est 0x1310.

Nous pouvons deviner la différence entre la version vulnérable et la version corrigée en calculant la taille, à l'aide de la formule : number_of_operations * 4.
Dans ce cas, l'allocation dans la version corrigée sera de 0x34 * 4 = 0x68 plus grande que dans la version vulnérable.

Après cela, elle ajoute 0x24. Cette valeur est calculée de manière similaire dans les deux versions, vulnérable et corrigée.

Elle ajoute ensuite la constante 0xf dans les deux cas.

Jusqu'à présent, la taille dans cet exemple a été 0x1340.

Ensuite, elle atteint rpcxdr_OncRpcBufMgrpAllocate.

Elle se déplace ensuite vers r15.


Elle soustrait un et ajoute quatre. Elle compare ensuite avec 0x800.
Cette taille mal calculée n'est utilisée que si elle est supérieure à 0x800. Pour cette raison, seule une REQUÊTE COMPOSÉE déclenchera le bogue.

D'abord, elle alloue un pool avec la taille = 0x80 et le tag XdBD.

Enfin, elle alloue le pool pour la réponse ici avec la taille 0x1398, qui ajoute quelques valeurs constantes.

Elle alloue ensuite 0x13a0 (incluant le tag XdBP et l'en-tête).

À partir de là, elle stocke l'adresse du nouveau pool alloué dans le champ : tag_XdBD_0x80.p_TAG_XDBP_0x13a0.

Cela pointe vers l'adresse de la réponse dans laquelle elle copie toujours.

Ensuite, elle commence à construire l'en-tête de la réponse.

Voici un exemple de la façon dont elle enregistre les données dans le contenu d'un pointeur temporaire et ajoute quatre.


Ci-dessous, nous pouvons voir comment elle copie dans le contenu de l'adresse de réponse.

Cela écrit le premier dword et augmente le pointeur de quatre.

Ensuite, elle écrit le deuxième dword et ajoute quatre.


Après être sortie de la fonction, l'en-tête entier est écrit.

Après cela, elle retourne dans nfssvr.sys pour continuer à écrire la réponse.

Elle continue à décoder et à écrire dans la réponse, en ajoutant quatre au pointeur temporaire.

Lorsqu'elle a terminé l'en-tête, elle atteint cette boucle pour écrire toutes les opérations. Elle commence par le premier OPCODE 0x35.


Nous pouvons voir qu'elle écrit 0x428 à partir du début du pool.

Maintenant, elle pointe après le tag.


En plaçant un point d'arrêt ici, nous pouvons voir comment toutes les opérations ont été écrites.


Après être sortie de la boucle, toutes les opérations sont copiées.

Vérifions la fin du pool.

Là, nous pouvons voir l'écriture après la limite.

L'allocation est plus petite que les données copiées, produisant un débordement de pool.
Cela provoque un BSOD sur la machine cible. Cependant, la question est : pouvons-nous réaliser une exécution de code à distance, ou un « Write what where » ?
J'ai essayé un certain nombre de combinaisons d'opcodes pour obtenir une réponse avec des données contrôlées dans les octets débordés. Malheureusement, je n'ai pas eu de chance.
Le tag maximum (contrôlé par moi) ne peut être placé qu'au début et a une taille maximale de 0x400.

Tous les autres opcodes que j'ai essayés ne répondent pas avec des données contrôlées. Par conséquent, je ne pense pas qu'il soit possible, ou du moins, qu'il soit extrêmement difficile d'obtenir une RCE ou d'élever des privilèges avec ce bogue. Cela dit, cela reste peut-être possible, car je n'ai pas essayé toutes les combinaisons parmi le grand nombre de possibilités qui existent.
Pour la construction du POC, j'ai essayé avec un client nommé « NFS CLIENT ». Il prend en charge NFS 4.1 et j'ai pu essayer différents opcodes en copiant des fichiers, en modifiant, en créant des dossiers, etc.

Dans cette construction, j'ai pu créer un paquet COMPOUND d'exemple et ajuster la taille, l'identifiant client, l'identifiant de session, etc.

Ensuite, j'ai envoyé un EXCHANGE_ID pour obtenir l'identifiant client, l'utilisant pour envoyer un CREATE_SESSION et enfin la grande REQUÊTE COMPOSÉE.

À ce stade, nous avons exploité le bogue, ce qui conduit à une exécution de code à distance permettant une attaque par déni de service (DoS).
Nous espérons que cela vous sera utile, si vous avez des doutes, vous pouvez nous contacter à [email protected].
Profitez-en !