Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
cve-2023-5217-poc — 브라우저의 WebCodecs 또는 MediaRecorder 인터페이스에서 CVE-2023-5217을 트리거하기 위한 PoC | Kitploit
도구/GitHubGitHub/ut-security/cve-2023-5217-poc
Vulnerability AnalysisExploitationWeb Application ExploitationFuzzingLearning & EducationBinary Exploitation
GitHubut-security/cve-2023-5217-poc

cve-2023-5217-poc

브라우저의 WebCodecs 또는 MediaRecorder 인터페이스에서 CVE-2023-5217을 트리거하기 위한 PoC

저장소 보기
16312년 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2023-5217: libvpx VP8 인코딩 힙 오버플로우 PoC

CVE-2023-5217은 야생에서 악용된 libvpx 취약점으로, Google 위협 분석 그룹(Threat Analysis Group)의 Clément Lecigne에 의해 Chrome을 대상으로 하는 것으로 발견되었습니다.

이 저장소는 WebCodecs 및 MediaRecorder API를 사용하여 브라우저에서 CVE-2023-5217을 트리거하는 방법을 보여줍니다. CVE-2023-5217은 제어 가능한 오버플로우 길이와 반복되는 작은 4바이트 값의 덮어쓰기가 가능한 힙 버퍼 오버플로우를 허용합니다. 현재 CVE-2023-5217이 야생에서 어떻게 악용되었는지는 알려져 있지 않습니다.

공개 당시 libvpx에 두 개의 패치와 Chromium에 한 개의 패치가 적용되어 CVE-2023-5217을 수정했습니다. libvpx 패치에는 VP8 스레드 수 변경 비활성화 및 멀티스레드 인코딩을 위한 테스트가 포함되었습니다. Chromium 패치는 WebCodecs에서 스레드 수 조정을 비활성화했습니다.

libvpx v1.13.0 Ugly Duckling의 근본적인 문제

요약

libvpx는 VP8/VP9 인코딩 및 디코딩을 처리하는 라이브러리입니다.

CVE-2023-5217의 핵심 문제는 libvpx VP8 인코딩 세션에서 스레드 수를 줄이면서 프레임 높이를 증가시키면 제어 가능한 길이의 선형 힙 오버플로우와 반복되는 작은 4바이트 값의 제어 가능한 덮어쓰기가 발생한다는 점입니다. 프레임 높이의 차이가 덮어쓰기의 길이를 제어하고, 새 프레임 너비가 반복적으로 쓰이는 4바이트 값을 제어합니다. 이 취약점은 이후 구성에서 높이를 줄여 다른 작은 4바이트 값을 지속적으로 쓰기 위해 여러 번 악용될 수 있습니다.

세부 사항

libvpx VP8 인코더는 인코더 스레드가 작업 중인 현재 열을 저장하는 mt_current_mb_col이라는 배열을 유지합니다. 이 배열은 스레드가 두 개 이상인 경우에만 할당되며, 크기는 mb_rows의 함수입니다. 여기서 mb_rows = frame_height >> 4이며 frame_height는 가장 가까운 16의 배수로 반올림됩니다.

root@kitploit:~
// https://github.com/webmproject/libvpx/blob/6512f994da13e2f27e6a7bd449efee0a374b55b7/vp8/common/alloccommon.c#L85
// 너비와 높이는 16의 배수로 반올림된 후 `mb_rows`와 `mb_cols`에 할당됩니다.
int vp8_alloc_frame_buffers(VP8_COMMON *oci, int width, int height) {
    ...
    // 너비/높이를 가장 가까운 16의 배수로 반올림
    if ((width & 0xf) != 0) width += 16 - (width & 0xf);
    if ((height & 0xf) != 0) height += 16 - (height & 0xf);
    ...
    oci->mb_rows = height >> 4;
    oci->mb_cols = width >> 4;
    ...
}
// https://github.com/webmproject/libvpx/blob/6512f994da13e2f27e6a7bd449efee0a374b55b7/vp8/encoder/onyx_if.c#L1232
// 이 스니펫은 `mt_current_mb_col`의 할당을 보여줍니다.
void vp8_alloc_compressor_data(VP8_COMP *cpi) {
  ...
  // 스레드가 2개 이상인 경우에만 할당
  if (cpi->oxcf.multi_threaded > 1) {
    int i;

    vpx_free(cpi->mt_current_mb_col);
    // sizeof(*cpi->mt_current_mb_col)은 4입니다.
    CHECK_MEM_ERROR(&cpi->common.error, cpi->mt_current_mb_col,
                    vpx_malloc(sizeof(*cpi->mt_current_mb_col) * cm->mb_rows));
    for (i = 0; i < cm->mb_rows; ++i)
      vpx_atomic_init(&cpi->mt_current_mb_col[i], 0);
  }
  ...
}

libvpx가 프레임 인코딩을 완료하면 인코딩된 열 수에 mt_sync_range를 더한 값을 mt_current_mb_col에 저장합니다.

root@kitploit:~
// https://github.com/webmproject/libvpx/blob/6512f994da13e2f27e6a7bd449efee0a374b55b7/vp8/encoder/onyx_if.c#L1212
// 너비를 기반으로 mt_sync_range 값이 설정되는 스니펫입니다.
void vp8_alloc_compressor_data(VP8_COMP *cpi) {
    ...
#if CONFIG_MULTITHREAD
  if (width < 640) {
    cpi->mt_sync_range = 1;
  } else if (width <= 1280) {
    cpi->mt_sync_range = 4;
  } else if (width <= 2560) {
    cpi->mt_sync_range = 8;
  } else {
    cpi->mt_sync_range = 16;
  }
#endif
  ...
}
// https://github.com/webmproject/libvpx/blob/6512f994da13e2f27e6a7bd449efee0a374b55b7/vp8/encoder/encodeframe.c#L560
// 공격자가 선택한 값이 쓰이는 함수입니다.
static void encode_mb_row(...) {
  ...
  const int nsync = cpi->mt_sync_range; // 이 값은 vp8_alloc_compressor_data에서 설정됩니다.
  vpx_atomic_int rightmost_col = VPX_ATOMIC_INIT(cm->mb_cols + nsync);
  ...
  if (vpx_atomic_load_acquire(&cpi->b_multi_threaded) != 0) {
    current_mb_col = &cpi->mt_current_mb_col[mb_row];
  }
  ...
  if (vpx_atomic_load_acquire(&cpi->b_multi_threaded) != 0) {
    // current_mb_col은 mt_current_mb_col에 대한 참조입니다.
    vpx_atomic_store_release(current_mb_col,
                             vpx_atomic_load_acquire(&rightmost_col));
  }
  ...
}

mt_current_mb_col을 오버플로우시키기 위해서는 세 가지 인코딩 구성이 필요합니다.

  1. configinit: 초기화 중에 libvpx는 initial_width와 initial_height를 설정합니다. 이는 이후 구성의 최대 가능 범위입니다. 여기서 스레드 수는 중요하지 않습니다. 이후의 너비/높이 조합이 시작보다 클 수 없기 때문에 중간 구성이 필요합니다.
  2. configvuln: 재구성 중에 두 개 이상의 스레드가 사용되면 libvpx는 configvuln.height를 기반으로 취약한 mt_current_mb_col 할당을 생성합니다. 이 새 높이는 configinit.initial_height보다 작아야 합니다. 그렇지 않으면 오류가 발생합니다.
  3. configattack: 재구성 중에 하나의 스레드만 사용되면 libvpx는 mt_current_mb_col을 재할당하지 않아 취약한 상태로 남깁니다. libvpx는 다음 조건이 성립할 때 이전에 할당된 범위를 벗어나 (configattack.width >> 4) + 1 (여기서 1은 변수 mt_sync_range이고 너비는 가장 가까운 16의 배수로 반올림됨) 값을 반복적으로 씁니다:
root@kitploit:~
\text{ceil}(\text{config}_{\text{init}}.\text{height}/16) \geq \text{ceil}(\text{config}_{\text{attack}}.\text{height}/16) \gt \text{ceil}(\text{config}_{\text{vuln}}.\text{height}/16)$$

mt_current_mb_col overflow

더 구체적으로, configinit으로 width = 1200, height = 1200, threads = 4로 VP8 인코딩 구성을 초기화한다고 가정합니다. 공격은 다음과 같습니다.

  1. configvuln은 width = 500 (512로 반올림), height = 700 (704로 반올림), threads = 2로 인코더를 재구성합니다. 변수 mb_rows는 704/16 = 44로 설정되고, 배열 mt_current_mb_col은 (44)*4 = 176바이트로 할당됩니다. mt_current_mb_col에 저장되는 값은 512/16 + 1 = 33입니다.
  2. configattack은 width = 18 (32로 반올림), height = 1000 (1008로 반올림), threads = 1로 인코더를 재구성합니다. mt_current_mb_col은 스레드가 두 개 이상일 때만 재할당되므로 동일한 크기를 유지하지만, mb_rows는 이제 1008/16 = 63으로 설정됩니다. libvpx가 encode_mb_row를 호출하면 mt_current_mb_col 할당을 63 - 44 * 4 = 68바이트 초과하여 쓰며, 값 32/16 + 1 = 3을 반복적으로 씁니다. 여기서 32는 반올림된 너비이고 1은 mt_sync_range 값입니다.
  3. 공격자는 configattack보다 작지만 여전히 configvuln보다 큰 높이를 사용하여 이 취약점을 다시 악용하여 다른 값을 쓸 수 있습니다. 예를 들어, width = 34, height = 990으로 configattack'을 생성하여 mb_rows = 992/16 = 62, mb_cols = 48/16 = 3으로 설정하고, 원래 할당을 62 - 44 * 4 = 64바이트 초과하여 값 4를 쓸 수 있습니다.

익스플로잇

이 취약점을 악용하려면 공격자가 인코딩 높이, 너비 및 스레드 수를 제어할 수 있어야 합니다. 전자 두 가지는 간단하지만, 후자는 인코딩 스레드 수가 재구성되는 위치를 찾아야 합니다.

root@kitploit:~
// https://github.com/webmproject/libvpx/blob/67bfb41ed8598edfb25bd6f245f9c39a68808548/vp8/vp8_cx_iface.c#L301
static vpx_codec_err_t set_vp8e_config(VP8_CONFIG *oxcf,
 ...
  oxcf->multi_threaded = cfg.g_threads;

Firefox

Firefox에서는 VP8TrackEncoder에서 인코딩하는 프레임 영역을 조정하여 스레드 수를 제어할 수 있습니다. 프레임 영역이 307,200(640x480 프레임)보다 크고 머신에 코어가 2개 이상인 경우 두 개 이상의 스레드가 사용됩니다.

root@kitploit:~
// https://searchfox.org/mozilla-central/source/dom/media/encoder/VP8TrackEncoder.cpp#97
nsresult CreateEncoderConfig(...) {
  ...
  int32_t number_of_cores = PR_GetNumberOfProcessors();
  if (aWidth * aHeight > 1920 * 1080 && number_of_cores >= 8) {
    config->g_threads = 4;  // 1080p 초과 시 4개 스레드.
  } else if (aWidth * aHeight > 1280 * 960 && number_of_cores >= 6) {
    config->g_threads = 3;  // 1080p에서 3개 스레드.
  } else if (aWidth * aHeight > 640 * 480 && number_of_cores >= 3) {
    config->g_threads = 2;  // qHD/HD에서 2개 스레드.
  } else {
    config->g_threads = 1;  // VGA 이하에서 1개 스레드.
  }
  ...

MediaRecorder API가 VP8TrackEncoder에 의존한다는 것을 발견했으며, 녹화 중인 캔버스의 크기를 변경하여 너비와 높이를 조정할 수 있습니다. 이를 호출하는 방법은 아래 MediaRecorder 섹션을 참조하십시오.

Chrome

Chrome도 인코딩되는 프레임 영역을 기준으로 코어 수에 맞게 스레드 수를 조정합니다.

root@kitploit:~
// https://source.chromium.org/chromium/chromium/src/+/main:media/video/vpx_video_encoder.cc;l=84
EncoderStatus SetUpVpxConfig(...) {
  ...
  // 이미지 너비와 코어 수를 기준으로 스레드 수를 설정합니다.
  config->g_threads = GetNumberOfThreadsForSoftwareEncoding(opts.frame_size);
}

// https://source.chromium.org/chromium/chromium/src/+/main:media/base/video_encoder.cc;drc=f5bdc89c7395ed24f1b8d196a3bdd6232d5bf771;l=33
int GetNumberOfThreadsForSoftwareEncoding(gfx::Size frame_size) {
  int area = frame_size.GetCheckedArea().ValueOrDefault(1);
  // VGA 미만에서는 기본적으로 1개 스레드.
  int desired_threads = 1;

  if (area >= 3840 * 2160) {
    desired_threads = 16;
  } else if (area >= 2560 * 1080) {
    desired_threads = 8;
  } else if (area >= 1280 * 720) {
    desired_threads = 4;
  } else if (area >= 640 * 480) {
    desired_threads = 2;
  }

  // 사용 가능한 논리 프로세서/코어 수로 제한합니다.
  desired_threads =
      std::min(desired_threads, base::SysInfo::NumberOfProcessors());

  return desired_threads;
}

이 경로는 WebCodecs VideoEncoding API를 통해 활성화되며, 인코딩 너비/높이를 직접 수정할 수 있습니다. 작동 방식은 WebCodecs 섹션을 참조하십시오.

MediaRecorder

mediarecorder.html 파일은 캔버스에서 MediaRecorder 세션을 생성하고 너비/높이를 조정하여 VP8 인코딩 재구성을 트리거하고 취약한 브라우저에서 CVE-2023-5217을 트리거하는 방법을 보여줍니다. 캔버스 너비 및 높이 매개변수를 조정할 때 setTimeout을 사용하여 VP8 인코딩 세션이 재구성될 충분한 시간을 확보합니다. 신뢰성을 위해 타임아웃 매개변수를 조정할 수 있습니다.

상태

  • ✅ Firefox: Firefox 렌더러에서 충돌을 유발합니다.
  • ❌ Chromium 기반 브라우저: Chromium 기반 브라우저는 MediaRecorder 세션에서 인코더를 재구성할 때 스레드 수를 변경하지 않습니다 [코드].
  • ❌ Safari: WebKit은 MediaRecorder 세션에서 VP8을 지원하지 않습니다 [코드].

Firefox 데모

Firefox에서 테스트하려면 fuzzfetch를 사용하여 이 CVE가 패치되기 전의 ASAN 빌드를 fuzzfetch --build 2023-09-27 -a 명령어로 가져온 다음, mediarecorder.html을 직접 열면 됩니다.

Firefox 데모

WebCodecs

webcodecs.html 및 webcodecs.js 파일은 Worker에서 WebCodecs API를 사용하여 취약한 브라우저에서 CVE-2023-5217을 트리거하는 방법을 보여줍니다. WebCodecs에서는 MediaRecorder보다 프레임 인코딩 호출을 더 많이 제어할 수 있지만, 세 단계를 각각 수행하기 위해 여전히 타임아웃에 의존합니다.

상태

  • ✅ Chromium 기반 브라우저: 탭 충돌을 유발합니다. WebCodecs에 대한 이 Chromium 패치는 초기 트라이지에 포함되었습니다.
  • ❌ Firefox: Firefox는 WebCodecs 인코딩을 지원하지 않습니다(디코딩은 설정 플래그 뒤에 활성화됨).
  • 🚧 Safari: Safari는 WebCodecs 인코딩을 지원하지만 충돌을 재현할 수 없었습니다.

Chromium 데모

Chromium에서 테스트하려면 get_asan_chrome.py를 사용하여 python get_asan_chrome.py --version 117.0.5938.131 명령어로 취약한 버전의 Chrome을 가져올 수 있습니다. 그런 다음 SSL을 사용하는 로컬 HTTP 서버를 시작해야 합니다. 서버 키를 생성하고 서버를 시작하려면 gen_server_key.sh 및 server.py를 참조하십시오. 그런 다음 취약한 Chromium에서 페이지를 열어 결과를 확인할 수 있습니다.

Chromium 데모

WebCodecs + MediaRecorder 결합

combined.html을 참조하십시오. 이 파일은 WebCodecs를 찾을 수 없을 때 MediaRecorder를 대체 수단으로 사용합니다. 이 결합 파일은 동일한 페이지에서 Chrome과 Firefox를 모두 대상으로 하는 데 사용됩니다.

결론

이 취약점은 복잡한 미디어 라이브러리를 원격 공격자에게 노출하는 데 따른 문제와 위험을 보여줍니다. RLBox와 같은 도구를 사용하면 브라우저가 미디어 라이브러리의 잠재적인 취약점을 격리할 수 있습니다. Firefox는 이미 선택된 라이브러리에서 이를 제공합니다.

읽어주셔서 감사합니다! 기여를 환영합니다. 다른 통찰력이 있으면 Issue를 제기하거나 PR을 열어 주십시오. 이 작은 4바이트 덮어쓰기가 어떻게 코드 실행으로 이어질 수 있는지 탐구하는 것이 남은 과제입니다.

이전 초안에 대한 피드백을 주신 Anand Balaji에게 감사드립니다.

도구 다운로드