우리 팀은 최근에 발견했습니다
팀은 오픈 소스 프로젝트를 지속적으로 모니터링하여 새로운 취약점과 악성 패키지를 찾고 이를 더 넓은 커뮤니티와 공유하여 전반적인 보안 상태를 개선합니다.
2부로 구성된 이 블로그 시리즈는 취약한 Xpm 파일 형식의 내부 작동에 대한 세부 정보를 제공하고 이러한 취약점을 악용하는 방법에 대해 자세히 설명합니다.
X Window System이라고도 불리는 Xorg X11은 Unix 계열 운영 체제에서 그래픽 사용자 인터페이스를 생성하고 관리할 수 있는 오픈 소스 그래픽 서버 프로토콜입니다. 이는 네트워크 환경에서 그래픽 응용 프로그램 실행, Windows 관리 및 사용자 입력 처리를 위한 프레임워크를 제공합니다.
libx11 패키지는 클라이언트 응용 프로그램이 데스크탑에 데이터를 렌더링하고 표시하는 데 필요한 필수 공유 라이브러리를 제공합니다.
libXpm은 X Pixmap (XPM) 형식의 이미지를 읽고, 쓰고, 표시하는 기능을 제공합니다.
XPM은 주로 투명 픽셀을 지원하는 아이콘 픽스맵을 생성하는 것을 목표로 합니다. 이는 XBM 구문을 기반으로 하며 XPM2 형식의 일반 텍스트 파일이거나 C 프로그래밍 언어 구문을 사용할 수 있으므로 C 프로그램 파일에 포함하기에 적합합니다.
XPM 이미지 형식 – 버전
전임자 – XBM
1989년 XPM(X PixMap)이 등장하기 전에는 XBM 형식(X BitMap)이 있었습니다.
X GUI에서 사용된 아이콘 및 커서 비트맵을 저장하는 데 사용되는 일반 텍스트 이진 이미지 형식입니다.
XBM 파일은 C 소스 파일로 구성됩니다. 이것이 오늘날 대부분의 이미지 형식과의 주요 차이점입니다. 이렇게 하면 애플리케이션에 직접 통합될 수 있습니다. 그러나 이로 인해 압축을 사용할 수 없다는 사실(1문자에서 1바이트 매핑)도 원시 픽셀 데이터보다 훨씬 큽니다.
X BitMap(XBM) 데이터는 원시 흑백 픽셀 정보를 저장하는 일련의 정적 부호 없는 문자 배열로 구성됩니다.
예를 들어 다음 C 코드는 이미지의 hello
에 대한 XBM 파일입니다.
#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 };
일반적인 이미지 파일 형식 헤더 대신 XBM 파일에는 #define
문이 있습니다. 첫 번째 값 쌍은 비트맵의 픽셀 크기를 정의하여 높이와 너비를 나타냅니다.
X BitMap(XBM) 이미지 데이터는 정적 배열 내에 저장되는 픽셀 값의 연속 시퀀스로 구성됩니다. 각 픽셀이 단일 비트(0은 흰색을 나타내고 1은 검은색을 나타냄)로 기호화된다는 점을 고려하면 배열의 각 바이트는 8개의 개별 픽셀에 대한 데이터를 포함합니다. 특히 첫 번째 바이트의 최하위 비트는 비트맵 내 왼쪽 위 픽셀의 앵커 역할을 합니다.
XPM 버전을 만나보세요
1989년에 처음 출시된 이 형식은 위에서 설명한 XBM 형식과 많은 유사점을 공유합니다. XBM은 흑백인 반면 XPM1은 이미지에 색상을 도입했습니다.
색상에 추가 매크로와 변수를 사용하고 픽셀 데이터의 경우 바이트를 문자로 바꿉니다.
예를 들어, 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 ", " ", " ", " ", " ", " ", " ", " ", " " };
여기서 XFACE_ncolors
이미지의 색상 수를 나타내고 XFACE_chars_per_pixel
픽셀당 문자 수를 나타냅니다.
각 a
흰색 "#ffffff"로 대체되고, 각 b
검정색 "#000000"으로 대체됩니다.
1년 후인 1990년에는 상황을 개선하기 위해 형식의 두 번째 버전이 설정되었습니다. 작업을 단순화하고 이미지 형식에서 모든 C 코드를 제거했습니다.
단순화된 구조:
! XPM2 <Header> <Colors> <Pixels>
헤더 라인은 XPM1의 #define 문과 유사한 이미지 크기를 나타냅니다.
색상 섹션은 문자 값을 정의합니다.
새로운 유형 개념이 도입되었습니다.
c
– 컬러 픽셀용입니다.
m
- 단색용입니다.
g
– 그레이스케일용 얼음
s
– 상징적입니다
기호 기능은 상황에 따라 색상을 지정하고 읽기 쉽게 변수 이름을 만드는 데 사용됩니다.
이전 예에 표시된 것과 동일한 hello
포함된 XPM2 이미지의 예:
! 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
16진수 색상 코드 외에도 X11 색상 이름(예: red
)을 사용하여 색상을 지정할 수도 있으며 None
투명도를 나타냅니다.
이것은 XPM 형식의 현재 버전입니다.
1991년에 출시된 버전은 C 코드를 다시 가져왔지만 순수한 C 코드 스타일을 사용하는 대신 내부 값은 본질적으로 XPM2 형식과 동일합니다.
예:
/* 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 ", " ", " ", " ", " ", " ", " ", " ", " " };
논의한 바와 같이 XPM 형식은 JFrog 로고와 같은 보다 정교한 이미지를 나타낼 수도 있습니다.
DoS 취약점 – CVE-2023-43786
CVE-2023-43786 취약점은 본질적으로 잘못된 재귀 중지 조건 계산으로 인해 발생하는 무한 루프입니다.
고정 커밋:
https://gitlab.freedesktop.org/xorg/lib/libx11/-/commit/204c3393c4c90a29ed6bef64e43849536e863a86
XPutImage
일반적으로 X Window인 X Drawable에 이미지를 배치할 수 있는 libX11의 함수입니다. 이 기능을 사용하면 XImage 구조의 픽셀 정보를 창이나 픽스맵과 같은 지정된 드로어블로 전송하고 필요에 따라 위치를 지정할 수 있습니다.
xpmCreatePixmapFromImage
xpmCreatePixmapFromImage
libXpm 함수는 이 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); }
이 함수에서 ximage
표시할 소스 이미지 픽셀 데이터이며 X Drawable
객체(이 경우 pixmap_return
)에 복사됩니다.
Xput이미지
다음은 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);......... }
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); } }
다음 예시 이미지를 살펴보겠습니다.
Available [the requested size] = (65,536 * 4) - 28 = 262,116 bits_per_pixel = 32 width = 90,000 pixels height = 1 pixel
이미지 bits_per_pixel
이 32이므로 [1]의 조건문이 통과되지 않아 [2]에 정의된 대체 코드 블록을 입력하게 됩니다.
그런 다음 [3]의 BytesPerRow
계산한 다음 이를 8로 나눕니다. 이 예에서는 BytesPerRow = 90000 * 32 / 8 = 360,0
이 예에서는 360000이 요청된 크기 262116보다 작지 않고 요청된 너비의 한 줄을 단일 요청에 맞출 수 없기 때문에 [4]에 대한 검사가 통과되지 않습니다. 이로 인해 [5에서 else
가 시작됩니다. ].
이는 단일 요청에 포함될 수 있는 픽셀 수를 결정합니다. 그런 다음 해당 하위 집합만 전달하기 위해 PutSubImage
함수에 대한 재귀 호출을 시작한 다음, 줄의 나머지 부분을 관리하기 위한 후속 재귀 호출을 시작합니다. 필요한 경우 이 나머지 부분은 추가 재귀 호출을 통해 추가로 나눌 수도 있습니다.
그러나 [6]의 계산에서는 픽셀당 비트 수를 고려하지 못했고 재귀 호출에서는 2096928비트 대신 2096928픽셀을 보내는 요청을 수행합니다. 이는 단일 요청에 맞는 것보다 큽니다.
이로 인해 픽셀 라인을 분할하려고 시도하는 끝없는 루프가 발생하여 지속적으로 숫자가 너무 커서 맞지 않을 수 있으며 동일한 값으로 프로세스를 다시 시도합니다. 이 재귀는 호출 스택이 소진될 때까지 지속됩니다.
버그 수정으로 인해 [6]의 계산이 변경되었으며 bits_per_pixel
을 고려했습니다. 이 예에서는 65529픽셀만 전송하도록 요청하는 재귀 호출이 발생하여 사용 가능한 공간에 완벽하게 맞는 262116의 BytesPerRow가 생성되므로 재귀가 단 2번의 호출로 진행되고 완료될 수 있습니다.
버그를 유발하는 개념 증명 이미지 예시: https://github.com/jfrog/jfrog-CVE-2023-43786-libX11_DoS/blob/main/cve-2023-43786.xpm
취약한 libXpm 라이브러리 함수를 호출하는 앱의 예로는 화면에 Xpm 이미지를 표시하는 데 사용되는 CLI 유틸리티 sxpm
있습니다.
이는 취약한 xpmCreatePixmapFromImage
Xpm 함수를 호출한 다음 취약한 libX11 함수 XPutImage
호출한 다음 PutSubImage
호출합니다.
여기에도 게시되었습니다 .