Codean Labs에서 2024년 5월 14일에 발표한 PDF.js의 취약점
취약점 한줄 요약
pdf 글꼴 변환 취약점을 이용한 악의적 코드가 주입된 pdf를 firefox 브라우져로 열람하거나 pdf 미리보기 웹 페이지에 접속시 XSS가 발생한다.
위험 수준은 어느정도일까
NVD (National Vulnerability Database)에서 아직 공식적인 CVSS 점수를 측정하지는 않았다
하지만 Mozila 보안담당자 측에서는 위험수준(high impact)으로 판단을 내렸다. 왜냐하면 공격자가 악성코드를 삽입하여 pdf 파일로 피해자의 시스템에 침입할수 있기 때문이다.
서론
pdf 파일은 매우 다양한 기능들을 가지고 있다
다양한 미디어 유형, 복잡한 글꼴 렌더링, 간단한 스크립트 까지 지원하기 때문에 취약성 연구에 많은 관심들이 쏟아지고 있다. 이렇게 많은 양의 구문 논리를 사용하면 실수(버그)가 있기 마련이고 pdf.js 파일 또한 예외는 아니다. 또한 C 나 C++와 같은 언어가 아닌 자바스크립트로 작성 되어 있기 때문에 메모리 손상 문제가 발생할 가능성이 매우 낮지만 다음과 같은 취약점이 발생할 수 있다.
문자 랜더링 (Glyph rendering)
Charcter 우리가 알고 있는 글자, glyph는 하나의 character에 대응되는 표현이다 glyph들이 모여 font를 만든다
해당 취약점은 pdf 형식의 스크립팅을 이용한 취약점이 아니고 글꼴 랜더딩 부분에서 일어난 취약점이다.
pdf에서는 다양한 글꼴을 제공하지만 일부 글꼴에 대한 지원이 힘들다.
Truetype와 같은 최신식 글꼴에 경우 PDF.js는 브라우져의 자체적 글꼴 렌더링으로 제공한다. 또는 glyph 메뉴얼을 수동으로 페이지의 곡선으로 바꿔야한다.
경로 생성 function이 모든 glyph에 대해 사전에 컴파일이 이루지는 데 지원 글꼴인 경우 [코드1]에 있는 자바스크립트 개체를 만들어서 수행이 된다.
//[코드1] // If we can, compile cmds into JS for MAXIMUM SPEED... if (this.isEvalSupported && FeatureTest.isEvalSupported) { const jsBuf = []; for (const current of cmds) { const args = current.args !== undefined ? current.args.join(",") : ""; jsBuf.push("c.", current.cmd, "(", args, ");\n"); } // eslint-disable-next-line no-new-func console.log(jsBuf.join("")); return (this.compiledGlyphs[character] = new Function( "c", "size", jsBuf.join("") )); } |
cmds에 제어해서 자체 코드를 삽입할 수 있다면 문자가 렌더링 되자마자 실행이 될 것이다.
해당 명령문이 어떻게 생겨났는지 파악이 필요하다 [코드 2]에 CompiledFont 클래스를 따라가다 보면 compileGlyph 메소드를 확인 할수 있다. 이 메소드는 cmds의 save,transform,scale, restore 명령문 배열을 초기화 하고 실제 렌더링 명령을 compileGlyphlmpl 메소드에 입력을 한다.
//[코드 2] compileGlyph(code, glyphId) { if (!code || code.length === 0 || code[0] === 14) { return NOOP; } let fontMatrix = this.fontMatrix; ... const cmds = [ { cmd: "save" }, { cmd: "transform", args: fontMatrix.slice() }, { cmd: "scale", args: ["size", "-size"] }, ]; this.compileGlyphImpl(code, cmds, glyphId); cmds.push({ cmd: "restore" }); return cmds; } |
pdf.js코드에서 [코드 2]에 함수가 동작하는 것을 기록해보면 [코드 3]에 명령어들이 사용되는 것을 확인할수 있다.
//[코드 3] c.save(); c.transform(0.001,0,0,0.001,0,0); c.scale(size,-size); c.moveTo(0,0); c.restore(); |
//[코드 4] { cmd: "transform", args: fontMatrix.slice() }, |
이 fontMatrix 배열은 개체 본문에 ,(콤마)로 쪼개서(slice) 삽입을 한다.
원래는 숫자형 배열이 들어가도록 만들어졌지만 과연 항상 그럴까?
어떠한 문자열 형태든 그대로 들어갈수 있다
즉 자바스크립트 문법 오류(syntax error)가 나거나 최악에 경우 임의의 코드가 삽입될수 있다.
그렇다면 정말로 fontMatrix에 들어가는 데이터 값을 임의로 조작할수 있을까?
FontMatrix 함수 조사
[코드 5]는 Type1 글꼴의 파서의 내용으로 그 예시가 될수 있다.
//[코드 5] extractFontHeader(properties) { let token; while ((token = this.getToken()) !== null) { if (token !== "/") { continue; } token = this.getToken(); switch (token) { case "FontMatrix": const matrix = this.readNumberArray(); properties.fontMatrix = matrix; break; ... } ... } ... } |
[코드 5]에 글꼴에는 기술적으로 헤더에 임의의 Postscript 코드가 포함되어 있지만 정상적인 pdf리더는 완벽하게 지원하지 않으며 대부분은 키-값(key-value) 형태로 읽을려고 한다. 이 경우 pdf.js는 키를 발견하면 숫자배열을 읽는다. 다른 글꼴 형식에서도 이 와 유사한 것으로 보인다 그래서 숫자에만 국한된 것처럼 보인다.
하지만 이 행렬에 원인은 단 한가지 이유만으로 끝나지 않았다. 확실한건 FontMatrix 글꼴 함수 외부, 즉 PDF 메타데이터 개체에서도 값을 변경 하는 것도 가능하다!
PartialEvaluator.translateFont(...)메소드를 자세히 보면 pdf 안에는 글꼴과 관련된 다양한 속성들이 존재하는것을 알수 있다. [코드 6]은 fontMatrix에서의 일부이다.
//[코드 6] const properties = { type, name: fontName.name, subtype, file: fontFile, ... fontMatrix: dict.getArray("FontMatrix") || FONT_IDENTITY_MATRIX, ... bbox: descriptor.getArray("FontBBox") || dict.getArray("FontBBox"), ascent: descriptor.get("Ascent"), descent: descriptor.get("Descent"), xHeight: descriptor.get("XHeight") || 0, capHeight: descriptor.get("CapHeight") || 0, flags: descriptor.get("Flags"), italicAngle: descriptor.get("ItalicAngle") || 0, ... }; |
PDF 형식에서 글꼴 정의는 여러 객체로 존재한다. Font, FontDescriptor와 실제 FontFile 객체로 말이다. 서술 된 순서대로 [코드 7]에 써보겠다
//[코드 7] //Font << /Type /Font /Subtype /Type1 /FontDescriptor 2 0 R /BaseFont /FooBarFont >> //fontDescriptor << /Type /FontDescriptor /FontName /FooBarFont /FontFile 3 0 R /ItalicAngle 0 /Flags 4 >> /// FontFile << /Length 100 >> ... (actual binary font data) ... |
font 객체에 대해 [코드 7]에 나온 대로 우리가 직접 FontMatrix 배열에 정의를 하자면 다음 [코드 8]와 같다
//[코드 8] << /Type /Font /Subtype /Type1 /FontDescriptor 2 0 R /BaseFont /FooBarFont /FontMatrix [1 2 3 4 5 6] % <----- >> |
[코드 8]을 시도할 떄 처음에는 작동이 안되는 것처럼 보인다. 왜냐하면 생성된 함수 본문의 변형 작업이 여전히 디폴트 행렬을 이용하기 때문이다. 하지만 [코드8]의 내용이 글꼴 파일 값을 덮어 씌우는 것이기 때문에 발생한다. 다행히도 내부의 FontMatrix 정의가 없는 Type1 글꼴을 사용하게 되면, PDF에서 지정된 값이 권한을 갖기 때문에 fontMatrix 값이 [코드 8]을 덮어 쓰이지 않는다.
이제 PDF 객체에서 이 배열을 장악할 수 있으므로 PDF는 숫자 형 배열 뿐 아니라 문자형 배열을 넣을수 있다. 이제 숫자형 배열 말고 문자열 배열을 넣어 볼것이다. (문자열은 PDF 내에서 괄호로 구분된다.)
//[코드 9] /FontMatrix [1 2 3 4 5 (foobar)] |
함수 본문에 삽입 성공이 되었다!
//[코드 10] c.save(); c.transform(1,2,3,4,5,foobar); c.scale(size,-size); c.moveTo(0,0); c.restore(); |
공격 실습
이제 임의의 자바스크립트 코드 삽입이 되었으니 문법(Syntax)만 잘 맞추기만 하면 된다.
//[코드 11] /FontMatrix [1 2 3 4 5 (0\); alert\('foobar')] |
실습을 위해 해당 취약점에 맞는 firefox 브라우져가 필요하다
실습 브라우져 다운로드 링크
https://mozilla-firefox.kr.uptodown.com/windows/versions
가능한 125 이하 버전 브라우져를 이용하도록 하며
자동 업데이트는 반드시 꺼주고 실습을 진행한다.
온라인 웹 pdf 미리보기 실습과 다운받은 pdf 열람 실습으로 총 두 가지 실습을 진행할 수 있다.
- pdf 미리보기 웹 페이지 접속
(실습 링크: https://codeanlabs.com/wp-content/uploads/2024/05/poc_generalized_CVE-2024-4367.pdf )

Node 모듈에 번들로 제공되는 형태이다.
NPM(Node Package Manager)에 따르면 매주 약 270만건의 다운로드가 이루어진다. 이 양식에서는 웹사이트는 내장 PDF 미리보기 기능을 제공하기 위해 이를 사용할 수 있다. Git 호스팅 플랫폼부터 메모 어플리케이션까지 이 양식을 이용한다. 아마도 PDF.js 또한 이 양식을 사용하고 있을 것이다.
실습 링크에 pdf 파일을 코드형태를 열어보면 다음과 같다
2. 다운받은 pdf 파일 브라우져로 열기 실습
pdf.js 는 firefox 내장된 뷰어이다. 그래서 Firefox 브라우져로 다운로드 받은 pdf를 열람시 XSS가 실행이 된다.
실습 pdf 파일 코드를 보면 alert이 삽입된 것을 확인 할 수 있다.
보완 조치
- PDF.js를 버전 4.2.67 이상으로 업데이트하는 것입니다
-
PDF.js 설정 isEvalSupported을 false. 이렇게 하면 취약한 코드 경로가 비활성화됩니다
- 파이어폭스 브라우져를 126버전 이상 최신형으로 업데이트를 한다.
참고
https://codeanlabs.com/blog/research/cve-2024-4367-arbitrary-js-execution-in-pdf-js/
https://github.com/s4vvysec/CVE-2024-4367-POC/blob/main/malicious.pdf?short_path=4b5ed9f
'보안 > 모의해킹' 카테고리의 다른 글
서브 도메인 검색 사이트 (0) | 2024.11.30 |
---|---|
Proxmark3 로 출입증 복사하기 (0) | 2024.05.15 |
CVE-2015-8249 취약점 분석 (1) | 2023.11.30 |
SNMP 정보 노출 취약점 (port 161 ) (0) | 2023.11.14 |
Method 허용 취약점 , webdav로 실습 (0) | 2023.11.11 |