- Chrome 안정 업데이트에 포함된 단일 보안 수정 CVE-2023-4863은 WebP 이미지 라이브러리의 힙 버퍼 오버플로우이며, Google은 이미 실제 환경에서 익스플로잇이 존재한다고 밝힘
- Apple SEAR의 2023년 9월 6일 보고와 Apple의 CVE-2023-41064, Citizen Lab의 BLASTPASS 정황이 맞물려 같은 버그일 가능성이 높음
- 취약점은 WebP 무손실 압축 VP8L의 Huffman 테이블 구성 과정에서 사전 계산된 버퍼 크기를 넘는 쓰기가 가능했던 문제로 분석됨
- 일반 퍼징이 놓치기 쉬웠던 이유는 여러 개의 최대 크기 Huffman 테이블과 특정 무효 테이블을 순서대로 만들어야 하는 까다로운 트리거 조건 때문임
- upstream libwebp 패치는 충분해 보이지만 libwebp가 브라우저·운영체제·앱에 널리 쓰이므로 패치 확산과 샌드박싱이 중요함
Chrome 패치가 BLASTPASS와 이어지는 이유
- Google은 2023년 9월 초 Chrome 안정 업데이트에서 Apple Security Engineering and Architecture(SEAR)가 보고한 CVE-2023-4863을 수정함
- 취약점은 WebP 이미지 라이브러리의 힙 버퍼 오버플로우임
- Google은 “CVE-2023-4863의 익스플로잇이 실제 환경에 존재함”을 알고 있다고 밝힘
- 같은 시기 Citizen Lab은 Washington DC 기반 시민사회단체 소속 개인의 iPhone에서 의심스러운 동작을 탐지함
- BLASTPASS는 iMessage 제로클릭 제로데이 익스플로잇으로 NSO Group의 Pegasus 스파이웨어를 배포한 사례와 연결됨
- Citizen Lab이 기술 분석 결과를 Apple에 전달한 뒤, Apple은 9월 7일 보안 공지에서 두 개의 CVE를 공개함
- Apple의 첫 번째 CVE인 CVE-2023-41061은 PassKit 첨부 파일에 이미지 익스플로잇을 묶어 iMessage의 BlastDoor 샌드박스를 우회한 정황과 맞닿아 있음
- 악성 이미지는 샌드박스가 없는 다른 프로세스에서 처리된 것으로 보임
- 두 번째 CVE인 CVE-2023-41064는 Apple ImageIO의 버퍼 오버플로우 취약점임
- ImageIO는 여러 이미지 형식을 판별하고 적절한 디코더에 연결하는 Apple의 이미지 파싱 프레임워크임
- 기술 세부 정보가 없어 CVE-2023-41064가 어떤 이미지 형식에 영향을 주는지는 확인되지 않음
- Apple이 최근 ImageIO에 WebP 지원을 추가했고, Apple 보안팀이 9월 6일 Chrome에 WebP 취약점을 보고했으며, Google이 5일 만에 긴급 패치하고 실제 악용으로 표시한 점 때문에 BLASTPASS와 CVE-2023-4863이 같은 버그일 가능성이 높음
libwebp 패치가 가리킨 취약점 위치
- Chrome 보안 공지의 버그 ID와 libwebp 오픈소스 커밋을 대조하면 Fix OOB write in BuildHuffmanTable 패치가 CVE-2023-4863에 해당함
- 패치는 Apple 보고 다음 날인 2023년 9월 7일 생성됨
- 취약점은 WebP의 무손실 압축 지원인 VP8L에 있음
- WebP 무손실 압축은 픽셀을 100% 정확도로 저장·복원하기 위해 Huffman coding을 사용함
- 최신 구현은 개념적 트리 구조 대신 테이블을 사용해 디코딩을 최적화함
- 취약 버전은 고정 테이블에서 가져온 사전 계산 버퍼 크기로 메모리를 할당한 뒤, 그 공간에 Huffman 테이블을 직접 구성함
- 새 버전은 첫 번째 패스에서 출력 테이블에 필요한 전체 크기를 계산하되 실제 쓰기는 하지 않음
- 전체 크기가 사전 계산 버퍼보다 크면 더 큰 할당을 만들도록 바뀜
- 핵심 흐름은
src/dec/vp8l_dec.c의ReadHuffmanCodes에서 할당되는huffman_tables와ReadHuffmanCode안의VP8LBuildHuffmanTable/BuildHuffmanTable호출임- Huffman 테이블은 서로 다른 알파벳 크기를 가진 5개 세그먼트로 나뉨
- 오버플로우를 만들려면 이 5개 테이블을 모두 정교하게 구성해야 함
트리거 파일을 만드는 과정
- WebP 무손실 압축은 입력 픽셀의 빈도 분석을 바탕으로 자주 나오는 값에는 짧은 비트열을, 드문 값에는 긴 비트열을 배정함
- 디코더가 비트열 길이를 항상 구분할 수 있도록 코드가 구성됨
- 압축 이미지에는 코드와 값의 매핑을 재현하기 위한 통계 정보와 코드 배정 정보가 포함되어야 함
- WebP는 Huffman 테이블 자체도 파일 크기를 줄이기 위해 Huffman coding으로 압축함
- 이 구조 때문에 취약점을 분석하고 트리거하는 과정이 복잡해짐
@mistymntncop은 임의의 Huffman coding 데이터, 즉 code lengths를 가진 정상 형식 WebP를 만드는 하네스 코드를 제공함- 이 하네스로 목표
BuildHuffmanTable호출에 임의의code_lengths배열을 전달할 수 있었음
- 이 하네스로 목표
- 수동 실험에서는
BuildHuffmanTable내부의 히스토그램,num_open,num_nodes,ReplicateValue의 시작 위치를 추적하는key값 사이의 상호작용이 매우 복잡했음count[0]부터count[8]까지의 root table은total_size에 직접 큰 영향을 주지는 않지만 이후 내부 상태를 바꿀 수 있음count[9]부터count[15]까지의 second level tables는 최종total_size에 직접 영향을 줌
- Mark Adler의 enough.c는 알파벳 크기, root table 크기, 최대 코드 길이에 대해 가능한 최대 Huffman 트리 lookup table의 히스토그램을 출력함
- libwebp의
kTableSize는 이 도구로 계산된 값이라고 주석에 적혀 있음 - 해당 도구로 사전 계산 버퍼 크기를 재현할 수 있었고,
@mistymntncop의 도구로 “enough”가 만든 code lengths가huffman_tables할당을 100% 채우는 것도 확인됨
- libwebp의
실제 오버플로우가 발생하는 조건
enough도구는 유효하고 완전한 코드에 대한 최대값을 계산함- 작은 테이블 중 하나인 심볼 크기 40, root table 8비트, 최대 코드 길이 15의 최대 크기는 410임
BuildHuffmanTable이 유효하다고 보는 코드 중 410보다 큰 크기를 만드는 사례는 발견되지 않음
- 오버플로우는 유효한 Huffman 트리만으로 만들기보다, 마지막 단계에서 무효 Huffman 트리를 넣을 때 발생함
- 4개의 유효한 Huffman 트리로 최대 크기의 출력 테이블을 먼저 만듦
- 마지막 테이블에 무효 Huffman 트리를 넣으면 최종 일관성 검사 전에
ReplicateValue가 범위를 벗어나 쓸 수 있음
- 재현은 취약한 libwebp 커밋
7ba44f80f3b94fc0138db159afea770ef06532a0을 체크아웃하고 AddressSanitizer를 켠 뒤,@mistymntncop의 PoC 코드로bad.webp를 생성해dwebp로 디코딩하는 방식임- AddressSanitizer는
BuildHuffmanTable에서heap-buffer-overflow를 보고함 - 보고된 쓰기는 11816바이트 영역 바로 뒤 주소에서 발생함
- AddressSanitizer는
huffman_tables를 오버플로우시키는 입력은 여러 개 존재함- 발견된 code lengths 중에는
huffman_tables할당 끝에서 400바이트 뒤까지 쓰는 사례가 있음 - 쓰는 값에 대한 제어가 부분적이어도 익스플로잇 가능해 보임
- 발견된 code lengths 중에는
- 무효 입력의 Huffman 트리는 부분적으로 불균형하며, 불균형 브랜치의 한 구간에 자식이 없는 내부 노드가 많이 포함됨
- 이 구조가 유효한 트리로는 도달할 수 없는
key인덱스를 만듦
- 이 구조가 유효한 트리로는 도달할 수 없는
패치가 오버플로우를 막는 방식
- 처음에는 패치가 필요한 크기에 맞춰 버퍼를 동적으로 키워 힙 오버플로우를 막는 방식처럼 보였음
- 실제로는 패치된 버전의 첫 번째 패스가
BuildHuffmanTable을 실행하면서 필요한 전체 크기를 계산하되 테이블에는 쓰지 않음- 오버플로우를 일으키던 무효 입력은 이 첫 번째 패스에서
BuildHuffmanTable이 실패하고 0을 반환함 - 첫 번째 패스는 쓰기를 하지 않기 때문에 무효 트리가 부분 처리되어도 범위 밖 쓰기가 발생하지 않음
- 오버플로우를 일으키던 무효 입력은 이 첫 번째 패스에서
- 유효하고 완전한 코드 중 같은 오버플로우를 일으키는 사례는 찾지 못했기 때문에, 이 패치는 충분해 보임
퍼징이 놓치기 쉬웠던 이유
- libwebp는 Google OSS-Fuzz에서 오랫동안 퍼징되어 왔고, WebP 무손실 지원도 광범위하게 퍼징되고 있었음
- 핵심 난점은 포맷과 트리거 조건이 모두 복잡하다는 점임
- 수십억 개 가능성 중에서 알파벳 크기 280과 256에 대해 최대 크기의 Huffman 테이블 4개를 먼저 구성해야 함
- 이어 알파벳 크기 40에 대해 매우 특정한 형태의 무효 Huffman 테이블을 만들어야 함
- 어느 단계에서든 1비트만 틀려도 이미지 디코더가 오류를 내고 안전하게 종료함
- Google은 WebP 0day 수정 후 WebP Huffman 루틴 전용 새 퍼저를 릴리스함
- 해당 퍼저를 실행해도 CVE-2023-4863은 발견되지 않았음
- 표준적인 비트플립 변이와 코드 커버리지 피드백 루프, AFL++의 CmpLog 같은 입력-상태 기반 접근도 이 극단적인 상태까지 중간 단계를 통과하기 어려움
- Quarkslab의 TritonDSE 같은 동적 심볼릭 실행 기법은 가능성이 있을 수 있으나 확인되지는 않음
- FreeType의 Load_SBit_Png 취약점과는 성격이 다름
- Load_SBit_Png는 퍼징 하네스가 API 사용 방식을 충분히 반영하지 못해 발견되지 않았음
- CVE-2023-4863은 하네스 부족보다는 취약점 자체의 제약 때문에 퍼징이 어려운 사례에 가까움
영향 범위와 대응 상태
- Citizen Lab은 실제 환경에서 사용된 고급 익스플로잇을 포착했고, Apple과 Chrome은 며칠 안에 수십억 사용자에게 업데이트를 배포한 것으로 보임
- Android는 당시 아직 영향을 받을 가능성이 남아 있었음
- Android의 BitmapFactory는 Apple ImageIO처럼 이미지 디코딩을 처리하고 libwebp를 지원함
- 당시 Android 보안 공지는 CVE-2023-4863 수정을 포함하지 않았지만, 수정은 AOSP에 병합되어 있었음
- Android가 영향을 받는다면 Signal이나 WhatsApp 같은 앱의 원격 익스플로잇으로 이어질 수 있음
- 10월 보안 공지에서 수정될 것으로 예상됨
- upstream libwebp의 패치는 올바르게 적용된 것으로 보이며, 필요한 곳으로 확산 중임
- libwebp는 많은 곳에서 사용되기 때문에 패치가 충분히 보급되기까지 시간이 걸릴 수 있음
- 이미지 디코딩처럼 제로클릭 원격 익스플로잇 공격 표면에 가까운 복잡한 파서 코드는 퍼징만으로 보안을 보장하기 어렵고, 선제적 소스 코드 리뷰와 적절한 샌드박싱 투자가 필요함
기술 정보 공개의 비대칭
- 벤더가 기술 세부 정보를 충분히 공개하지 않으면 방어자가 취약점 영향을 검증하기 어려워짐
- 공격자는 N-day 취약점을 추적하고 익스플로잇하려는 동기가 강하며, 세부 정보가 공개되지 않아도 크게 늦춰지지 않음
- 방어자는 이런 수준의 기술 분석을 수행할 자원이 부족한 경우가 많음
- 기본적인 공격 동작 정보까지 숨기면 공격자가 방어자보다 취약점과 익스플로잇에 대한 통찰을 더 많이 갖는 비대칭이 생김