Nhóm của tôi gần đây đã phát hiện ra , thư viện đồ họa phổ biến rộng rãi – (có mức độ nghiêm trọng NVD cao ). 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. hai lỗ hổng bảo mật trong X.Org libX11 CVE-2023-43786 và CVE-2023-43787 CVSS 7.8 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 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. nguồn mở 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. 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 (XPM). X Pixmap 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 trong hình ảnh: hello #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 . 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ó. #define 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 trắng tương tự ở định dạng XPM1: hello /* 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 ", " ", " ", " ", " ", " ", " ", " ", " " }; ở đây biểu thị số lượng màu trong ảnh và biểu thị số ký tự trên mỗi pixel. XFACE_ncolors XFACE_chars_per_pixel Mỗi sẽ được thay thế bằng màu trắng “#ffffff”, và mỗi sẽ được thay thế bằng màu đen “#000000”. a b 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: – dành cho pixel màu c – dành cho đơn sắc m – băng cho thang độ xám g – mang tính biểu tượng s 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 như trong các ví dụ trước: hello ! 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ụ: ), với biểu thị độ trong suốt. red None 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: 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 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. XPutImage xpmCreatePixmapFromImage Hàm libXpm gọi hàm này: xpmCreatePixmapFromImage 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); } Trong chức năng này, 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 (trong trường hợp này ). ximage X Drawable pixmap_return XPutImage Đây là chức năng libX11: XPutImage 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 Vì 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]. bits_per_pixel Sau đó, nó tính toán trên [3] rồi chia cho 8. Trong ví dụ của chúng tôi: BytesPerRow = 90000 * 32/8 = 360,0 BytesPerRow 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 trên [5 ]. else Đ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 để 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. PutSubImage 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 , 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. một vòng lặp vô tận khi cố gắng phân chia dòng pixel đã thay đổi phép tính trên [6] và tính đến . 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. Bản sửa lỗi bits_per_pixel 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 , được sử dụng để hiển thị hình ảnh Xpm trên màn hình. sxpm Nó gọi hàm XPM dễ bị tấn công, sau đó gọi hàm libX11 dễ bị tấn công là rồi đến . xpmCreatePixmapFromImage XPutImage PutSubImage Cũng được xuất bản ở đây.