paint-brush
Dévoilement d'une vulnérabilité vieille de 35 ans dans nix libX11 : partie 1par@yairjfrog
319 lectures
319 lectures

Dévoilement d'une vulnérabilité vieille de 35 ans dans nix libX11 : partie 1

par Yair Mizrahi18m2024/03/08
Read on Terminal Reader

Trop long; Pour lire

Cette série de blogs se penche sur les vulnérabilités de sécurité trouvées dans X.Org libX11, à savoir CVE-2023-43786 et CVE-2023-43787, explorant les subtilités du format de fichier XPM et démontrant l'exploitation de ces vulnérabilités. Découvrez comment protéger vos systèmes grâce aux informations complètes et aux correctifs fournis.
featured image - Dévoilement d'une vulnérabilité vieille de 35 ans dans nix libX11 : partie 1
Yair Mizrahi HackerNoon profile picture
0-item


Mon équipe a récemment découvert deux vulnérabilités de sécurité dans X.Org libX11 , la bibliothèque graphique très populaire – CVE-2023-43786 et CVE-2023-43787 (avec une gravité NVD élevée CVSS 7.8 ). Ces vulnérabilités provoquent un déni de service et l'exécution de code à distance. Les dernières versions de X11 contiennent des correctifs pour ces vulnérabilités.


L'équipe surveille en permanence les projets open source pour détecter de nouvelles vulnérabilités et packages malveillants et les partage avec la communauté au sens large pour aider à améliorer leur posture de sécurité globale.


Cette série de blogs en deux parties fournit des détails sur le fonctionnement interne du format de fichier vulnérable Xpm et approfondit l'exploitation de ces vulnérabilités.


Qu’est-ce que libX11 ?

Xorg X11, souvent appelé système X Window, est un protocole de serveur graphique open source qui permet la création et la gestion d'interfaces utilisateur graphiques dans les systèmes d'exploitation de type Unix. Il fournit un cadre pour exécuter des applications graphiques, gérer Windows et gérer les entrées des utilisateurs dans un environnement en réseau.


Le package libx11 offre les bibliothèques partagées essentielles dont les applications clientes ont besoin pour restituer et présenter les données sur votre bureau.


Architecture Client_Serveur X


Fenêtre Xterm - une application client X11


Qu’est-ce que libXpm ?

libXpm fournit des fonctions pour lire, écrire et afficher des images au format X Pixmap (XPM).

XPM vise principalement à générer des pixmaps d'icônes avec prise en charge des pixels transparents. Il est basé sur la syntaxe XBM et peut être soit un fichier texte brut au format XPM2, soit utiliser une syntaxe de langage de programmation C, ce qui le rend approprié pour être inclus dans un fichier programme C.


Format d'image XPM – versions

Prédécesseur – XBM


Avant la création de XPM (X PixMap) en 1989, il existait le format XBM (X BitMap).


Un format d'image binaire en texte brut, utilisé pour stocker les bitmaps d'icônes et de curseurs utilisés dans l'interface graphique X.


Les fichiers XBM sont structurés comme des fichiers source C. C’est leur principale distinction par rapport à la plupart des formats d’image actuels. De cette façon, ils peuvent être intégrés directement dans les applications. Cependant, pour cette raison et du fait qu'aucune compression ne peut être utilisée (mappage de 1 caractère à 1 octet), ils sont également beaucoup plus volumineux que leurs données brutes de pixels.


Les données X BitMap (XBM) comprennent une séquence de tableaux de caractères statiques non signés qui stockent les informations brutes de pixels monochromes.


Par exemple, le code C suivant est le fichier XBM pour hello dans l'image :

bonjour l'image Xbm affichée dans XnView

 #define hello_width 35 #define hello_height 25 static char hello_bits[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x88, 0x00, 0x00, 0x10, 0x00, 0x88, 0x00, 0x00, 0x10, 0x00, 0x88, 0x00, 0x00, 0xD0, 0xE1, 0x88, 0x78, 0x00, 0x30, 0x13, 0x89, 0xC4, 0x00, 0x10, 0x12, 0x89, 0x84, 0x00, 0x10, 0xF2, 0x89, 0x84, 0x00, 0x10, 0x12, 0x88, 0x84, 0x00, 0x10, 0x12, 0x88, 0x44, 0x00, 0x10, 0xE2, 0x89, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

Au lieu des en-têtes de format de fichier image habituels, les fichiers XBM ont des instructions #define . La première paire de valeurs définit les dimensions en pixels du bitmap, indiquant sa hauteur et sa largeur.


Les données d'image X BitMap (XBM) sont structurées comme une séquence continue de valeurs de pixels, stockées dans un tableau statique. Étant donné que chaque pixel est symbolisé par un seul bit (0 indiquant le blanc et 1 représentant le noir), chaque octet du tableau englobe les données de huit pixels individuels. Notamment, le bit le moins significatif du premier octet sert d'ancre pour le pixel supérieur gauche dans le bitmap.


XPM1

Découvrez la version XPM


Sorti pour la première fois en 1989, il partage de nombreuses similitudes avec le format XBM décrit ci-dessus. Alors que le XBM était monochrome, le XPM1 introduisait des couleurs dans les images.


Il utilise des macros et des variables supplémentaires pour les couleurs et remplace les octets par des caractères pour les données de pixels.


Par exemple, voici la même image hello et blanc au format XPM1 :


 /* XPM */ static char *_hello[] = { /* columns rows colors chars-per-pixel */ "35 25 2 1 ", " c white", "bc black", /* pixels */ " ", " ", " ", " ", " ", " ", " ", " bbb ", " bbb ", " bbb ", " b bbb bbb bb bbbb ", " bb bb bbbbb bb ", " bbbbbbbb ", " bb bbbbb bbbb ", " bbbbbbb ", " bbbbbbb ", " bb bbbb bb bbb ", " ", " ", " ", " ", " ", " ", " ", " " };

XFACE_ncolors désigne ici le nombre de couleurs dans l'image et XFACE_chars_per_pixel désigne le nombre de caractères par pixel.


Chaque a sera remplacé par la couleur blanche « #ffffff », et chaque b sera remplacé par la couleur noire « #000000 ».


XPM2

Un an plus tard, en 1990, la deuxième version du format devait améliorer les choses. Cela a simplifié les choses et supprimé tout le code C du format d'image.


La structure simplifiée :

 ! XPM2 <Header> <Colors> <Pixels>


La ligne d'en-tête indique les dimensions de l'image similaires aux instructions #define de XPM1.

La section couleurs définit les valeurs des caractères.


Un nouveau concept de type a été introduit :

c – est pour le pixel de couleur

m – est pour monochrome

g – glace pour les niveaux de gris

s – est pour symbolique


La fonctionnalité symbolique est utilisée pour attribuer des couleurs par contexte et pour leur créer des noms de variables pour une lecture plus facile.


Exemple d'image XPM2, avec le même hello que celui présenté dans les exemples précédents :

 ! XPM2 35 25 2 1 ac #000000 bc #ffffff aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaabaaaaaaaaaaaaaabaaabaaaaaaaaaaa aaaabaaaaaaaaaaaaaabaaabaaaaaaaaaaa aaaabaaaaaaaaaaaaaabaaabaaaaaaaaaaa aaaababbbaaaabbbaaabaaabaaabbbbaaaa aaaabbaabbaabaaabaabaaabaabaaabbaaa aaaabaaaabaabaaabaabaaabaabaaaabaaa aaaabaaaabaabbbbbaabaaabaabaaaabaaa aaaabaaaabaabaaaaaabaaabaabaaaabaaa aaaabaaaabaabaaaaaabaaabaabaaabaaaa aaaabaaaabaaabbbbaabaaabaaabbbaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

Outre les codes de couleur hexadécimaux, les couleurs peuvent également être spécifiées en utilisant n'importe quel nom de couleur X11 (par exemple red ), None indiquant la transparence.


XPM3

Il s'agit de la version actuelle du format XPM.


La version, publiée en 1991, a ramené le code C, mais au lieu d'utiliser un style de code C pur, les valeurs à l'intérieur sont essentiellement les mêmes que dans le format XPM2.


Exemple:

 /* XPM */ static char *_hello[] = { /* columns rows colors chars-per-pixel */ "35 25 2 1 ", " c white", "bc black", /* pixels */ " ", " ", " ", " ", " ", " ", " ", " bbb ", " bbb ", " bbb ", " b bbb bbb bb bbbb ", " bb bb bbbbb bb ", " bbbbbbbb ", " bb bbbbb bbbb ", " bbbbbbb ", " bbbbbbb ", " bb bbbb bb bbb ", " ", " ", " ", " ", " ", " ", " ", " " };


Comme indiqué, le format XPM peut également représenter des images plus sophistiquées, comme le logo JFrog :

Logo JFrog représenté à l'aide de XPM3


Vulnérabilité DoS – CVE-2023-43786

La vulnérabilité CVE-2023-43786 est essentiellement une boucle sans fin résultant d'un calcul incorrect de la condition d'arrêt de récursion.


Validation corrigée :

https://gitlab.freedesktop.org/xorg/lib/libx11/-/commit/204c3393c4c90a29ed6bef64e43849536e863a86


XPutImage est une fonction de libX11 qui vous permet de placer des images sur un X Drawable, généralement une X Window. Avec cette fonction, on peut transférer les informations de pixels d'une structure XImage vers un dessin désigné, tel qu'une fenêtre ou une pixmap, et les positionner selon les besoins.


xpmCreatePixmapFromImage

La fonction xpmCreatePixmapFromImage libXpm appelle cette fonction XPutImage :

 void xpmCreatePixmapFromImage( Display *display, Drawable d, XImage *ximage, Pixmap *pixmap_return) { GC gc; XGCValues values; *pixmap_return = XCreatePixmap(display, d, ximage->width, ximage->height, ximage->depth); /* set fg and bg in case we have an XYBitmap */ values.foreground = 1; values.background = 0; gc = XCreateGC(display, *pixmap_return, GCForeground | GCBackground, &values); XPutImage(display, *pixmap_return, gc, ximage, 0, 0, 0, 0, ximage->width, ximage->height); XFreeGC(display, gc); }


Dans cette fonction, ximage correspond aux données de pixels de l'image source à afficher et est copiée dans l'objet X Drawable (dans ce cas pixmap_return ).


XPutImage

Voici la fonction XPutImage libX11 :

 int XPutImage ( register Display *dpy, Drawable d, GC gc, register XImage *image, int req_xoffset, int req_yoffset, int x, int y, unsigned int req_width, unsigned int req_height){ ..... PutSubImage(dpy, d, gc, &img, 0, 0, x, y, (unsigned int) width, (unsigned int) height, dest_bits_per_pixel, dest_scanline_pad); UnlockDisplay(dpy); SyncHandle(); Xfree(img.data); return 0; } }
 LockDisplay(dpy); FlushGC(dpy, gc); PutSubImage(dpy, d, gc, image, req_xoffset, req_yoffset, x, y, (unsigned int) width, (unsigned int) height, dest_bits_per_pixel, dest_scanline_pad);......... }


Il appelle la fonction PutSubImage :

 static void PutSubImage ( register Display *dpy, Drawable d, GC gc, register XImage *image, int req_xoffset, int req_yoffset, int x, int y, unsigned int req_width, unsigned int req_height, int dest_bits_per_pixel, int dest_scanline_pad) { int left_pad, BytesPerRow, Available; if ((req_width == 0) || (req_height == 0)) return; Available = ((65536 < dpy->max_request_size) ? (65536 << 2) : (dpy->max_request_size << 2)) - SIZEOF(xPutImageReq); if ((image->bits_per_pixel == 1) || (image->format != ZPixmap)) { [1] left_pad = (image->xoffset + req_xoffset) & (dpy->bitmap_unit - 1); BytesPerRow = (ROUNDUP((long)req_width + left_pad, dpy->bitmap_pad) >> 3) * image->depth; } else { [2] left_pad = 0; BytesPerRow = ROUNDUP((long)req_width * dest_bits_per_pixel, [3] dest_scanline_pad) >> 3; } if ((BytesPerRow * req_height) <= Available) { [4] PutImageRequest(dpy, d, gc, image, req_xoffset, req_yoffset, x, y, req_width, req_height, dest_bits_per_pixel, dest_scanline_pad); } else if (req_height > 1) { int SubImageHeight = Available / BytesPerRow; if (SubImageHeight == 0) SubImageHeight = 1; PutSubImage(dpy, d, gc, image, req_xoffset, req_yoffset, x, y, req_width, (unsigned int) SubImageHeight, dest_bits_per_pixel, dest_scanline_pad); PutSubImage(dpy, d, gc, image, req_xoffset, req_yoffset + SubImageHeight, x, y + SubImageHeight, req_width, req_height - SubImageHeight, dest_bits_per_pixel, dest_scanline_pad); } else { [5] int SubImageWidth = (((Available << 3) / dest_scanline_pad) [6] * dest_scanline_pad) - left_pad; PutSubImage(dpy, d, gc, image, req_xoffset, req_yoffset, x, y, (unsigned int) SubImageWidth, 1, dest_bits_per_pixel, dest_scanline_pad); PutSubImage(dpy, d, gc, image, req_xoffset + SubImageWidth, req_yoffset, x + SubImageWidth, y, req_width - SubImageWidth, 1, dest_bits_per_pixel, dest_scanline_pad); } }


Détails de la vulnérabilité technique

Prenons l'exemple d'image suivant :


 Available [the requested size] = (65,536 * 4) - 28 = 262,116 bits_per_pixel = 32 width = 90,000 pixels height = 1 pixel


Puisque l'image bits_per_pixel est de 32, l'instruction conditionnelle en [1] ne passera pas, nous amenant à entrer le bloc de code alternatif défini dans [2].


Il calcule ensuite le BytesPerRow sur [3] puis le divise par 8. Dans notre exemple : BytesPerRow = 90000 * 32 / 8 = 360,0


Dans l'exemple, la vérification sur [4] ne réussirait pas, car 360000 n'est pas inférieur à la taille demandée 262116, et n'est pas capable d'insérer une seule ligne de la largeur demandée dans une seule requête - cela lance le else sur [5 ].


Cela détermine le nombre de pixels pouvant être inclus dans une seule requête. Il lance ensuite un appel récursif à la fonction PutSubImage pour transmettre uniquement ce sous-ensemble, suivi d'un appel récursif ultérieur pour gérer la partie restante de la ligne. Si nécessaire, cette partie restante peut également être divisée par des appels récursifs supplémentaires.


Cependant, le calcul de [6] ne prend pas en compte les bits par pixel, et l'appel récursif effectue des requêtes envoyant 2 096 928 pixels au lieu de 2 096 928 bits, ce qui est plus grand que ce que peut contenir une seule requête.


Cela conduit à une boucle sans fin de tentatives de division de la ligne de pixels , ce qui entraîne systématiquement un nombre trop grand pour correspondre et réessaye le processus pour réessayer avec les mêmes valeurs. Cette récursion persiste jusqu'à ce que la pile d'appels soit épuisée.


Le correctif de bug a modifié le calcul sur [6] et a pris en compte le bits_per_pixel . Dans l'exemple, cela entraînerait un appel récursif demandant d'envoyer seulement 65 529 pixels, ce qui donnerait un BytesPerRow de 262 116 qui s'adapte parfaitement à l'espace disponible, permettant ainsi à la récursion de progresser et de se terminer en seulement 2 appels.


Exemple d'image de preuve de concept pour déclencher le bug : https://github.com/jfrog/jfrog-CVE-2023-43786-libX11_DoS/blob/main/cve-2023-43786.xpm


Comment le bug peut être déclenché

Un exemple d'application qui appelle la fonction vulnérable de la bibliothèque libXpm est l'utilitaire CLI sxpm , qui est utilisé pour afficher les images Xpm à l'écran.


Il appelle la fonction vulnérable xpmCreatePixmapFromImage Xpm, qui appelle ensuite les fonctions vulnérables libX11 XPutImage puis PutSubImage .

xpmCreatePixmapFromImage_XPutImage_PutSubImage


Également publié ici.