CVE-2024-4367 PDF.js任意JavaScript执行学习和复现过程
序言
该漏洞详细解释可见下面文章链接:
https://codeanlabs.com/2024/05/cve-2024-4367-arbitrary-js-execution-in-pdf-js/
由于pdf.js很常见,特别是在很多厂商,以及大厂都有可能用到这个查看器进行预览pdf文件,对于挖掘src上面的xss、客户端的rce特别有帮助,因此,记录一下该漏洞的学习过程
PDF.js 是由 Mozilla 维护的基于 JavaScript 的 PDF 查看器。该漏洞允许攻击者在打开恶意PDF文件后立即执行任意JavaScript代码。这影响了所有 Firefox 用户(<126),因为 Firefox 用 PDF.js 来显示 PDF 文件,但也严重影响许多基于 Web 和 Electron 的应用程序,这些应用间接使用PDF.js作为预览功能。
在挖洞或渗透测试中,这个漏洞的价值很高:
1、广泛的Web业务:许多网站(如代码托管平台、在线笔记应用)都使用了受影响版本的 PDF.js,可导致存储型或反射型XSS,窃取数据或接管账号。
2、Electron应用中的RCE:在未正确进行沙箱隔离的 Electron 应用中,该漏洞可进一步导致原生代码执行(RCE),危害极大。
漏洞成因
在PDF.js上下文中(与Javascript沙箱无关),PDF.js为了提高字体渲染性能,通过利用字形路径编译过程中缺乏类型检查来执行任意Javascript代码,特别是当涉及Type 1字体时。前提是 必须为真(默认是)。isEvalSupported,问题在于,编译过程中直接拼接了来自 PDF 文件的数据
漏洞分析过程
1、找到代码注入点
在 CompiledFont 类的 compileGlyph 方法中,会为每个字形预定义一个命令列表:
compileGlyph(code, glyphId) {
let fontMatrix = this.fontMatrix; // 数据会来自 PDF 文件
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;
}
fontMatrix 被直接作为 transform 命令的参数,稍后会拼接到代码中
2、命令列表会转代码(new Function 的位置)
compileGlyphToFunction(cmds) {
if (this.isEvalSupported && FeatureTest.isEvalSupported) {
const jsBuf = [];
for (const current of cmds) {
// args.join(",") 直接拼接,不检查数据类型
const args = current.args !== undefined ? current.args.join(",") : "";
jsBuf.push("c.", current.cmd, "(", args, ");\n");
}
// 动态创建函数,字符串变成代码
return new Function("c", "size", jsBuf.join(""));
}
// 安全路径:逐条解释执行
return function(c, size) { /* ... */ };
}
在这个位置,args.join(",") 会把数组中的所有元素直接拼成字符串。如果某个元素是字符串,它的内容会被直接拼接进去,不加任何引号。
3、找到可以控制输入的入口
我们要想的问题就是,我们需要怎么把我们想要的字符串传递进去?并且我们能否控制?
在最开始,我们知道利用了fontMatrix.slice(),并且使用ew Function动态创建函数,将fontMatrix.slice()传入这个动态函数中
所以我们需要找到fontMatrix
字体文件的内部
在Type1 字体解析器读取 FontMatrix 的代码
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;
...
}
...
}
...
}
readNumberArray() 强制要求纯数字数组,导致我们传入不了字符串,所以这个点不行
PDF 字典(突破口)
在 PartialEvaluator.translateFont 方法中
const properties = {
type,
name: fontName.name,
subtype,
file: fontFile,
...
// 从 PDF 的 Font 字典中读取 /FontMatrix
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,
...
};
dict.getArray() 是宽松解析,可以读取数字、字符串、名称等任意 PDF 数据类型
在PDF 语法中,字符串用括号 () 包裹
在这里就可以让攻击者在 PDF 的 /Font 字典中定义一个包含字符串的 /FontMatrix,从而就可以传入我们的恶意代码,比如alert()
漏洞复现过程
下面我们模拟一个企业的PDF预览功能进行复现该漏洞,以下均为靶场演示
靶场环境下载地址:
https://github.com/xiaoqiesec0x1/CVE-2024-4367-PDF.js-xss
生成poc的项目地址:
https://github.com/LOURC0D3/CVE-2024-4367-PoC
生成一个恶意的pdf

访问漏洞靶场环境

在实验环境中,有一个pdf格式文档上传并预览功能,我们生成好一个恶意的pdf文件之后,进行上传

成功触发我们想要执行的代码

并且通过抓包可以发现,上传的poc.pdf文件中,在FontMatrix处包含了我们想要执行的代码