
CVE-2026-74945, 조작된 웹 폰트를 통한 초기화되지 않은 힙 정보 노출 (sec-high)
이 디렉터리에는 Firefox 폰트 처리 과정에서 발생하는 초기화되지 않은 메모리 노출에 대한 정직한 재현 패키지가 담겨 있습니다. 조작된 @font-face 폰트가 OTS 출력 스트림을 초기화되지 않은 힙 영역의 빈틈으로 유도하고, 일반 콘텐츠가 캔버스 measureText API를 통해 그 빈틈의 바이트를 다시 읽어냅니다. 이 패키지는 로컬에 다운로드한 취약한 Firefox 빌드를 대상으로 합성 데이터만 사용하여 실행하도록 만들어졌습니다. 어떠한 실제 운영 중이거나 제3자의 시스템에도 접촉하지 않습니다.
일치하는 PoC 저장소: https://github.com/defineid/Palimpsest
분석 문서: 이 포트폴리오의 writeup-font-cmap-leak.html.
| 필드 | 값 |
|---|---|
| CVE | CVE-2026-74945 |
| 구성 요소 | Core / Graphics: Text |
| 분류 | 초기화되지 않은 메모리 노출 (CWE-908 · CWE-200) |
| 심각도 | sec-high (웹에서 도달 가능한 메모리 노출) |
| 수정 버전 | Firefox 154 · ESR 140.14 |
| 버그 | bugzilla.mozilla.org/show_bug.cgi?id=2057808 |
| 상호작용 | 없음 · 페이지 로드만으로 발생 |
Firefox는 사용 전에 OTS를 통해 웹 폰트를 정제합니다. 세 가지 독립적인 결함이 맞물려, 조작된 폰트가 정제된 출력 내부에 초기화되지 않은 힙 영역의 빈틈을 남기고 콘텐츠가 이를 다시 읽어낼 수 있게 됩니다:
moz_xrealloc)로 증가합니다.nonDefaultUVSOffset을 가진 cmap 포맷-14 서브테이블은 OTS가 정제된 폰트를 다시 직렬화할 때 출력 스트림을 앞으로 seek하게 만듭니다. 기록된 바이트와 seek 대상 사이의 건너뛴 구간은 초기화되지 않은 힙을 담은 채 호출자에게 전달되며, forget()은 버퍼 크기를 도달한 가장 높은 오프셋에 맞춥니다.cmap 테이블의 나머지 부분을 기준으로 경계를 검사하므로, idRangeOffset이 포맷-4 데이터의 끝을 넘어가는 세그먼트도 검사를 통과하여 인접한 서브테이블의 초기화되지 않은 바이트를 읽게 됩니다.조작된 폰트는 마지막 세그먼트(0xFFFF)가 기묘하게 범위를 넘어가는 idRangeOffset을 사용하는 포맷-4 서브테이블과, 큰 nonDefaultUVSOffset을 가진 포맷-14 서브테이블을 결합하며, 여기에 maxp.numGlyphs = 65535와 글리프 id별로 구별되는 hmtx advance(advance == glyph id)를 더합니다. 따라서 U+FFFF를 측정하면 초기화되지 않은 빈틈에서 읽힌 글리프 id로 해석되고, 스크립트에서 읽을 수 있는 그 advance 너비는 약 64 KB 창에 걸쳐 측정 한 번당 약 2바이트의 힙을 노출합니다.
일반적인 웹 페이지가 @font-face를 통해 조작된 폰트를 제공하고 ctx.measureText("")를 호출합니다. 클릭, 권한, 확장 기능, 환경 설정 변경이 필요하지 않습니다. OTS는 프로세스 내에서 실행되며 샌드박스 처리된 라이브러리에 속하지 않으므로, 노출된 바이트는 포인터나 비밀 정보를 포함할 수 있는 콘텐츠 프로세스의 힙입니다.
| 파일 | 용도 |
|---|---|
make_font.py | 조작된 폰트인 leak.otf와 leak.woff를 빌드합니다. |
trigger.html | @font-face로 폰트를 로드하고 measureText를 통해 U+FFFF advance를 다시 읽는 최소 페이지입니다. |
https://ftp.mozilla.org/pub/firefox/releases/140.9.0esr/에서 일치하는
플랫폼 빌드를 다운로드하십시오.pip install fonttools brotli
python3 make_font.py # writes leak.otf and leak.woff in this directory
그런 다음 취약한 빌드에서 file:// 경로 또는 직접 제어하는 로컬 정적 서버를 통해 trigger.html을 열고 Measure를 클릭합니다.
.notdef)으로 해석되고 advance는 0입니다. 출력 스트림이 0으로 초기화되고 format-4 조회가 자체 서브테이블로 제한되므로 갭이 누출되지 않습니다.make_font.py가 이곳에서 fontTools 4.63.0으로 실행되어 leak.otf(393816바이트)와 leak.woff(132372바이트)를 생성했습니다. 생성된 폰트는 원시 바이트에서 다시 파싱되었고 모든 조작된 요소가 확인되었습니다: maxp.numGlyphs = 65535, hhea.numberOfHMetrics = 65535, 글리프 id별로 구별되는 hmtx advance(advance == 글리프 id), 두 개의 cmap 서브테이블(platform 3 encoding 1 format 4, 그리고 platform 0 encoding 5 format 14), 홀수로 초과하는 idRangeOffset = 0x4001을 가진 format-4 마지막 세그먼트 endCode = 0xFFFF, 그리고 format-14 nonDefaultUVSOffset = 0x8000.trigger.html을 직접 실행하십시오. 이 패키지는 조작된 입력과 다시 읽기 경로를 보여주는 것으로, 무기화된 익스플로잇이 아니며 통제된 코드 실행 프리미티브를 포함하지 않습니다.이는 작성자가 직접 보고했으며 이미 패치된 CVE에 대한 기술 문서를 위해, 합성된 자체 생성 폰트 데이터를 사용하여 로컬로 다운로드한 빌드를 대상으로 합니다. 어떠한 라이브 또는 제3자 서비스도 공격하지 않습니다.