CVE-2024-4367 PDF.js任意JavaScript执行学习和复现过程

Posted

序言

该漏洞详细解释可见下面文章链接:

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

image-20260521000349646

访问漏洞靶场环境

image-20260521000241424

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

image-20260521000425302

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

image-20260521001131428

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