paint-brush
Enthüllung einer 35 Jahre alten Sicherheitslücke in nix libX11: Teil 1von@yairjfrog
339 Lesungen
339 Lesungen

Enthüllung einer 35 Jahre alten Sicherheitslücke in nix libX11: Teil 1

von Yair Mizrahi18m2024/03/08
Read on Terminal Reader

Zu lang; Lesen

Diese Blogserie befasst sich mit den Sicherheitslücken in X.Org libX11, nämlich CVE-2023-43786 und CVE-2023-43787, untersucht die Feinheiten des XPM-Dateiformats und demonstriert die Ausnutzung dieser Schwachstellen. Erfahren Sie, wie Sie Ihre Systeme mit umfassenden Einblicken und Korrekturen schützen können.
featured image - Enthüllung einer 35 Jahre alten Sicherheitslücke in nix libX11: Teil 1
Yair Mizrahi HackerNoon profile picture
0-item


Mein Team hat es kürzlich entdeckt zwei Sicherheitslücken in X.Org libX11 , die weit verbreitete Grafikbibliothek – CVE-2023-43786 und CVE-2023-43787 (mit einem hohen NVD-Schweregrad CVSS 7.8 ). Diese Schwachstellen verursachen einen Denial-of-Service und die Ausführung von Code aus der Ferne. Die neuesten Versionen von X11 enthalten Korrekturen für diese Schwachstellen.


Das Team überwacht ständig Open-Source-Projekte, um neue Schwachstellen und Schadpakete zu finden, und teilt sie mit der breiteren Community, um zur Verbesserung ihrer allgemeinen Sicherheitslage beizutragen.


Diese zweiteilige Blogserie bietet Details zum Innenleben des anfälligen Xpm-Dateiformats und geht tief in die Ausnutzung dieser Schwachstellen ein.


Was ist libX11?

Xorg X11, oft auch als X Window System bezeichnet, ist ein grafisches Open-Source -Serverprotokoll, das die Erstellung und Verwaltung grafischer Benutzeroberflächen in Unix-ähnlichen Betriebssystemen ermöglicht. Es bietet ein Framework für die Ausführung grafischer Anwendungen, die Verwaltung von Windows und die Verarbeitung von Benutzereingaben in einer Netzwerkumgebung.


Das libx11-Paket bietet die wesentlichen gemeinsam genutzten Bibliotheken, die Clientanwendungen zum Rendern und Präsentieren von Daten auf Ihrem Desktop benötigen.


Client_Server X-Architektur


Xterm-Fenster – eine X11-Clientanwendung


Was ist libXpm?

libXpm bietet Funktionen zum Lesen, Schreiben und Anzeigen von Bildern im X Pixmap (XPM)-Format.

XPM zielt in erster Linie darauf ab, Icon-Pixmaps mit Unterstützung für transparente Pixel zu generieren. Es basiert auf der XBM-Syntax und kann entweder eine reine Textdatei im XPM2-Format sein oder eine C-Programmiersprachensyntax verwenden, wodurch es für die Einbindung in eine C-Programmdatei geeignet ist.


XPM-Bildformat – Versionen

Vorgänger – XBM


Bevor XPM (X PixMap) im Jahr 1989 auf den Markt kam, gab es das XBM-Format (X BitMap).


Ein binäres Nur-Text-Bildformat, das zum Speichern von Symbol- und Cursor-Bitmaps verwendet wird, die in der X-GUI verwendet wurden.


XBM-Dateien sind als C-Quelldateien strukturiert. Dies ist ihr Hauptunterschied zu den meisten heutigen Bildformaten. Dadurch können sie direkt in Anwendungen eingebunden werden. Aus diesem Grund und aufgrund der Tatsache, dass keine Komprimierung angewendet werden kann (1-Zeichen-zu-1-Byte-Zuordnung), sind sie jedoch auch weitaus größer als ihre Rohpixeldaten.


X-BitMap-Daten (XBM) bestehen aus einer Folge statischer, vorzeichenloser Zeichenarrays, in denen die rohen monochromen Pixelinformationen gespeichert sind.


Der folgende C-Code ist beispielsweise die XBM-Datei für hello im Bild:

Hallo, Xbm-Bild wird in XnView angezeigt

 #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 };

Anstelle der üblichen Header im Bilddateiformat verfügen XBM-Dateien über #define Anweisungen. Das erste Wertepaar definiert die Pixelabmessungen der Bitmap und gibt deren Höhe und Breite an.


X-BitMap-Bilddaten (XBM) sind als kontinuierliche Folge von Pixelwerten strukturiert, die in einem statischen Array gespeichert werden. Da jedes Pixel durch ein einzelnes Bit symbolisiert wird (wobei 0 für Weiß und 1 für Schwarz steht), umfasst jedes Byte im Array die Daten für acht einzelne Pixel. Insbesondere dient das niedrigstwertige Bit des ersten Bytes als Anker für das obere linke Pixel innerhalb der Bitmap.


XPM1

Lernen Sie die XPM-Version kennen


Es wurde erstmals 1989 veröffentlicht und weist viele Ähnlichkeiten mit dem oben beschriebenen XBM-Format auf. Während das XBM monochrom war, brachte das XPM1 Farben in die Bilder ein.


Es verwendet zusätzliche Makros und Variablen für Farben und ersetzt Bytes durch Zeichen für Pixeldaten.


Hier ist zum Beispiel das gleiche hello im XPM1-Format:


 /* 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 bezeichnet hier die Anzahl der Farben im Bild und XFACE_chars_per_pixel bezeichnet die Anzahl der Zeichen pro Pixel.


Jedes a wird durch die weiße Farbe „#ffffff“ und jedes b durch die schwarze Farbe „#000000“ ersetzt.


XPM2

Ein Jahr später, 1990, sollte die zweite Version des Formats Abhilfe schaffen. Es hat die Dinge vereinfacht und den gesamten C-Code aus dem Bildformat entfernt.


Die vereinfachte Struktur:

 ! XPM2 <Header> <Colors> <Pixels>


Die Kopfzeile bezeichnet die Bildabmessungen ähnlich den #define-Anweisungen von XPM1.

Der Abschnitt „Farben“ definiert die Zeichenwerte.


Ein neues Typenkonzept wurde eingeführt:

c – steht für Farbpixel

m – steht für Monochrom

g – Eis für Graustufen

s – steht für symbolisch


Die symbolische Funktion wird verwendet, um Farben nach Kontext zuzuweisen und ihnen zur leichteren Lesbarkeit Variablennamen zu erstellen.


Beispiel eines XPM2-Images mit demselben hello wie in den vorherigen Beispielen:

 ! 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

Abgesehen von hexadezimalen Farbcodes können die Farben auch mit jedem der X11-Farbnamen (z. B. red ) angegeben werden, wobei None Transparenz angibt.


XPM3

Dies ist die aktuelle Version des XPM-Formats.


Die 1991 veröffentlichte Version brachte den C-Code zurück, aber anstatt einen reinen C-Codestil zu verwenden, sind die darin enthaltenen Werte im Wesentlichen dieselben wie im XPM2-Format.


Beispiel:

 /* 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 ", " ", " ", " ", " ", " ", " ", " ", " " };


Wie bereits erwähnt, kann das XPM-Format auch anspruchsvollere Bilder darstellen, beispielsweise das JFrog-Logo:

JFrog-Logo mit XPM3 dargestellt


DoS-Schwachstelle – CVE-2023-43786

Bei der Sicherheitslücke CVE-2023-43786 handelt es sich im Wesentlichen um eine Endlosschleife, die aus einer falschen Berechnung der Rekursionsstoppbedingung resultiert.


Festes Commit:

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


XPutImage ist eine Funktion in libX11, mit der Sie Bilder auf einem X-Drawable, normalerweise einem X-Fenster, platzieren können. Mit dieser Funktion kann man Pixelinformationen von einer XImage-Struktur auf ein bestimmtes Zeichenelement, wie ein Fenster oder eine Pixmap, übertragen und es nach Bedarf positionieren.


xpmCreatePixmapFromImage

Die xpmCreatePixmapFromImage libXpm-Funktion ruft diese XPutImage Funktion auf:

 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); }


In dieser Funktion sind ximage die anzuzeigenden Quellbildpixeldaten und werden in das X Drawable -Objekt (in diesem Fall pixmap_return ) kopiert.


XPutImage

Hier ist die XPutImage libX11-Funktion:

 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);......... }


Es ruft die PutSubImage Funktion auf:

 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); } }


Details zur technischen Schwachstelle

Nehmen wir das folgende Beispielbild:


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


Da das Bild bits_per_pixel 32 beträgt, wird die bedingte Anweisung bei [1] nicht bestanden, was dazu führt, dass wir den in [2] definierten alternativen Codeblock eingeben.


Anschließend berechnet es BytesPerRow auf [3] und dividiert es dann durch 8. In unserem Beispiel: BytesPerRow = 90000 * 32 / 8 = 360,0


Im Beispiel würde die Prüfung auf [4] nicht erfolgreich sein, da 360000 nicht kleiner als die angeforderte Größe 262116 ist und nicht in der Lage ist, eine einzelne Zeile der angeforderten Breite in eine einzelne Anfrage zu passen – dies initiiert die else auf [5 ].


Dies bestimmt die Anzahl der Pixel, die in einer einzelnen Anfrage enthalten sein können. Anschließend wird ein rekursiver Aufruf der PutSubImage Funktion initiiert, um nur diese Teilmenge zu übergeben, gefolgt von einem anschließenden rekursiven Aufruf, um den verbleibenden Teil der Zeile zu verwalten. Bei Bedarf kann dieser verbleibende Teil auch durch weitere rekursive Aufrufe weiter aufgeteilt werden.


Die Berechnung in [6] berücksichtigt jedoch nicht die Bits pro Pixel, und der rekursive Aufruf führt dazu, dass Anfragen 2096928 Pixel anstelle von 2096928 Bits senden – was mehr ist, als in eine einzelne Anfrage passen.


Dies führt zu einer Endlosschleife, in der versucht wird, die Pixelreihe zu teilen , was immer wieder dazu führt, dass die Zahl zu groß ist, um hineinzupassen, und der Vorgang erneut versucht wird, es mit den gleichen Werten zu versuchen. Diese Rekursion bleibt bestehen, bis der Aufrufstapel erschöpft ist.


Der Bugfix hat die Berechnung auf [6] geändert und die bits_per_pixel berücksichtigt. Im Beispiel würde dies dazu führen, dass ein rekursiver Aufruf nur 65529 Pixel senden soll, was zu einer BytesPerRow von 262116 führt, die perfekt in den verfügbaren Platz passt, sodass die Rekursion in nur zwei Aufrufen voranschreiten und abgeschlossen werden kann.


Beispielhaftes Proof-of-Concept-Bild zum Auslösen des Fehlers: https://github.com/jfrog/jfrog-CVE-2023-43786-libX11_DoS/blob/main/cve-2023-43786.xpm


Wie der Fehler ausgelöst werden kann

Ein Beispiel für eine App, die die anfällige libXpm-Bibliotheksfunktion aufruft, ist das CLI-Dienstprogramm sxpm , das zur Anzeige von Xpm-Bildern auf dem Bildschirm verwendet wird.


Es ruft die anfällige Xpm-Funktion xpmCreatePixmapFromImage auf, die dann die anfälligen libX11-Funktionen XPutImage und dann PutSubImage aufruft.

xpmCreatePixmapFromImage_XPutImage_PutSubImage