paint-brush
Hé lộ lỗ hổng 35 năm tuổi trong nix libX11: Phần 1từ tác giả@yairjfrog
339 lượt đọc
339 lượt đọc

Hé lộ lỗ hổng 35 năm tuổi trong nix libX11: Phần 1

từ tác giả Yair Mizrahi18m2024/03/08
Read on Terminal Reader

dài quá đọc không nổi

Loạt blog này đi sâu vào các lỗ hổng bảo mật được tìm thấy trong X.Org libX11, cụ thể là CVE-2023-43786 và CVE-2023-43787, khám phá sự phức tạp của định dạng tệp XPM và trình bày cách khai thác các lỗ hổng này. Tìm hiểu cách bảo vệ hệ thống của bạn bằng thông tin chi tiết và bản sửa lỗi toàn diện được cung cấp.
featured image - Hé lộ lỗ hổng 35 năm tuổi trong nix libX11: Phần 1
Yair Mizrahi HackerNoon profile picture
0-item


Nhóm của tôi gần đây đã phát hiện ra hai lỗ hổng bảo mật trong X.Org libX11 , thư viện đồ họa phổ biến rộng rãi – CVE-2023-43786 và CVE-2023-43787 (có mức độ nghiêm trọng NVD cao CVSS 7.8 ). Những lỗ hổng này gây ra hiện tượng từ chối dịch vụ và thực thi mã từ xa. Các phiên bản mới nhất của X11 chứa các bản sửa lỗi cho các lỗ hổng này.


Nhóm liên tục giám sát các dự án nguồn mở để tìm ra các lỗ hổng và gói độc hại mới, đồng thời chia sẻ chúng với cộng đồng rộng lớn hơn để giúp cải thiện tình trạng bảo mật tổng thể của họ.


Chuỗi blog gồm 2 phần này cung cấp thông tin chi tiết về hoạt động bên trong của định dạng tệp Xpm dễ bị tấn công và đi sâu vào việc khai thác các lỗ hổng này.


libX11 là gì?

Xorg X11, thường được gọi là X Window System, là giao thức máy chủ đồ họa nguồn mở cho phép tạo và quản lý giao diện người dùng đồ họa trong các hệ điều hành giống Unix. Nó cung cấp một khuôn khổ để chạy các ứng dụng đồ họa, quản lý Windows và xử lý thông tin đầu vào của người dùng trong môi trường nối mạng.


Gói libx11 cung cấp các thư viện chia sẻ thiết yếu mà ứng dụng khách yêu cầu để hiển thị và trình bày dữ liệu trên màn hình của bạn.


Kiến trúc Client_Server X


Cửa sổ Xterm - ứng dụng khách X11


libXpm là gì?

libXpm cung cấp các chức năng đọc, ghi và hiển thị hình ảnh ở định dạng X Pixmap (XPM).

XPM chủ yếu nhằm mục đích tạo ra các bản đồ ảnh biểu tượng có hỗ trợ các pixel trong suốt. Nó dựa trên cú pháp XBM và có thể là tệp văn bản thuần túy ở định dạng XPM2 hoặc sử dụng cú pháp ngôn ngữ lập trình C, làm cho nó phù hợp để đưa vào tệp chương trình C.


Định dạng hình ảnh XPM – các phiên bản

Tiền thân – XBM


Trước khi XPM (X PixMap) ra đời vào năm 1989, đã có định dạng XBM (X BitMap).


Định dạng hình ảnh nhị phân văn bản thuần túy, được sử dụng để lưu trữ bitmap biểu tượng và con trỏ được sử dụng trong X GUI.


Các tệp XBM được cấu trúc dưới dạng tệp nguồn C. Đây là điểm khác biệt chính của chúng so với hầu hết các định dạng hình ảnh hiện nay. Bằng cách đó, chúng có thể được tích hợp trực tiếp vào các ứng dụng. Tuy nhiên, do điều này và thực tế là không thể sử dụng tính năng nén (ánh xạ 1 ký tự thành 1 byte), chúng cũng lớn hơn nhiều so với dữ liệu pixel thô của chúng.


Dữ liệu X BitMap (XBM) bao gồm một chuỗi các mảng char không dấu tĩnh lưu trữ thông tin pixel đơn sắc thô.


Ví dụ: mã C sau đây là tệp XBM cho hello trong hình ảnh:

xin chào hình ảnh Xbm hiển thị trong 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 };

Thay vì các tiêu đề định dạng tệp hình ảnh thông thường, tệp XBM có câu lệnh #define . Cặp giá trị đầu tiên xác định kích thước pixel của bitmap, biểu thị chiều cao và chiều rộng của nó.


Dữ liệu hình ảnh X BitMap (XBM) được cấu trúc dưới dạng một chuỗi giá trị pixel liên tục, được lưu trữ trong một mảng tĩnh. Cho rằng mỗi pixel được ký hiệu bằng một bit duy nhất (với 0 biểu thị màu trắng và 1 biểu thị màu đen), mỗi byte trong mảng bao gồm dữ liệu cho tám pixel riêng lẻ. Đáng chú ý, bit có ý nghĩa nhỏ nhất của byte đầu tiên đóng vai trò là điểm neo cho pixel phía trên bên trái trong bitmap.


XPM1

Gặp phiên bản XPM


Được phát hành lần đầu tiên vào năm 1989, nó có nhiều điểm tương đồng với định dạng XBM đã nêu ở trên. Trong khi XBM là đơn sắc thì XPM1 đưa màu sắc vào hình ảnh.


Nó sử dụng các macro và biến bổ sung cho màu sắc và thay thế byte bằng ký tự cho dữ liệu pixel.


Ví dụ: đây là bức ảnh hello trắng tương tự ở định dạng 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 ở đây biểu thị số lượng màu trong ảnh và XFACE_chars_per_pixel biểu thị số ký tự trên mỗi pixel.


Mỗi a sẽ được thay thế bằng màu trắng “#ffffff”, và mỗi b sẽ được thay thế bằng màu đen “#000000”.


XPM2

Một năm sau, vào năm 1990, phiên bản thứ hai của định dạng này được thiết lập để cải thiện mọi thứ. Nó đơn giản hóa mọi thứ và loại bỏ tất cả mã C khỏi định dạng hình ảnh.


Cấu trúc đơn giản hóa:

 ! XPM2 <Header> <Colors> <Pixels>


Dòng tiêu đề biểu thị kích thước hình ảnh tương tự như câu lệnh #define của XPM1.

Phần màu sắc xác định các giá trị ký tự.


Một khái niệm kiểu mới đã được giới thiệu:

c – dành cho pixel màu

m – dành cho đơn sắc

g – băng cho thang độ xám

s – mang tính biểu tượng


Tính năng tượng trưng được sử dụng để gán màu theo ngữ cảnh và tạo tên biến cho chúng để dễ đọc hơn.


Ví dụ về hình ảnh XPM2, có cùng hello như trong các ví dụ trước:

 ! 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

Ngoài mã màu thập lục phân, các màu cũng có thể được chỉ định bằng cách sử dụng bất kỳ tên màu X11 nào (ví dụ: red ), với None biểu thị độ trong suốt.


XPM3

Đây là phiên bản hiện tại của định dạng XPM.


Phiên bản phát hành năm 1991 mang mã C trở lại, nhưng thay vì sử dụng kiểu mã C thuần túy, các giá trị bên trong về cơ bản giống như ở định dạng XPM2.


Ví dụ:

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


Như đã thảo luận, định dạng XPM cũng có thể thể hiện những hình ảnh phức tạp hơn, chẳng hạn như logo JFrog:

Logo JFrog được mô tả bằng XPM3


Lỗ hổng DoS – CVE-2023-43786

Lỗ hổng CVE-2023-43786 về cơ bản là một vòng lặp vô tận do tính toán điều kiện dừng đệ quy không chính xác.


Đã sửa lỗi cam kết:

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


XPutImage là một hàm trong libX11 cho phép bạn đặt hình ảnh vào X Drawable, thường là X Window. Với chức năng này, người ta có thể chuyển thông tin pixel từ cấu trúc XImage sang đối tượng có thể vẽ được chỉ định, như cửa sổ hoặc bản đồ ảnh và định vị nó nếu cần.


xpmCreatePixmapFromImage

Hàm xpmCreatePixmapFromImage libXpm gọi hàm XPutImage này:

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


Trong chức năng này, ximage là dữ liệu pixel hình ảnh nguồn sẽ được hiển thị và được sao chép vào đối tượng X Drawable (trong trường hợp này pixmap_return ).


XPutImage

Đây là chức năng 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);......... }


Nó gọi hàm 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); } }


Chi tiết về lỗ hổng kỹ thuật

Hãy lấy hình ảnh ví dụ sau:


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


bits_per_pixel của hình ảnh là 32 nên câu lệnh điều kiện tại [1] sẽ không vượt qua, dẫn đến việc chúng ta phải nhập khối mã thay thế được xác định trong [2].


Sau đó, nó tính toán BytesPerRow trên [3] rồi chia cho 8. Trong ví dụ của chúng tôi: BytesPerRow = 90000 * 32/8 = 360,0


Trong ví dụ, kiểm tra trên [4] sẽ không vượt qua, vì 360000 không nhỏ hơn kích thước được yêu cầu 262116 và không thể khớp một dòng có chiều rộng được yêu cầu vào một yêu cầu duy nhất - điều này sẽ bắt đầu yêu cầu else trên [5 ].


Điều này xác định số lượng pixel có thể được bao gồm trong một yêu cầu. Sau đó, nó bắt đầu lệnh gọi đệ quy đến hàm PutSubImage để chỉ chuyển tập hợp con đó, sau đó là lệnh gọi đệ quy tiếp theo để quản lý phần còn lại của dòng. Nếu cần, phần còn lại này cũng có thể được chia thêm thông qua các lệnh gọi đệ quy bổ sung.


Tuy nhiên, phép tính trên [6] không tính đến số bit trên mỗi pixel và lệnh gọi đệ quy thực hiện các yêu cầu gửi 2096928 pixel thay vì 2096928 bit - lớn hơn mức có thể phù hợp trong một yêu cầu.


Điều này dẫn đến một vòng lặp vô tận khi cố gắng phân chia dòng pixel , liên tục dẫn đến một số quá lớn để vừa và phải thử lại quá trình để thử lại với cùng các giá trị. Quá trình đệ quy này tiếp tục cho đến khi hết ngăn xếp cuộc gọi.


Bản sửa lỗi đã thay đổi phép tính trên [6] và tính đến bits_per_pixel . Trong ví dụ này, nó sẽ dẫn đến một cuộc gọi đệ quy yêu cầu chỉ gửi 65529 pixel, dẫn đến BytesPerRow là 262116 hoàn toàn phù hợp với không gian có sẵn, do đó cho phép đệ quy tiến hành chuyển tiếp và kết thúc chỉ sau 2 cuộc gọi.


Hình ảnh chứng minh ví dụ về cách kích hoạt lỗi: https://github.com/jfrog/jfrog-CVE-2023-43786-libX11_DoS/blob/main/cve-2023-43786.xpm


Làm thế nào lỗi có thể được kích hoạt

Một ví dụ về ứng dụng gọi hàm thư viện libXpm dễ bị tấn công là tiện ích CLI sxpm , được sử dụng để hiển thị hình ảnh Xpm trên màn hình.


Nó gọi hàm xpmCreatePixmapFromImage XPM dễ bị tấn công, sau đó gọi hàm libX11 dễ bị tấn công là XPutImage rồi đến PutSubImage .

xpmCreatePixmapFromImage_XPutImage_PutSubImage


Cũng được xuất bản ở đây.