CVE-2024-4367 PDF.js RCE 分析(译)

admin 2024年5月22日21:38:24评论458 views字数 5894阅读19分38秒阅读模式

CVE-2024-4367 – PDF.js中的任意JavaScript执行

CVE-2024-4367 PDF.js RCE 分析(译)

摘要

本文详细介绍了由Codean Labs发现的PDF.js中的CVE-2024-4367漏洞。PDF.js是由Mozilla维护的基于JavaScript的PDF查看器。这个漏洞允许攻击者在打开恶意PDF文件时立即执行任意JavaScript代码。这影响到了所有Firefox用户(版本小于126),因为Firefox使用PDF.js来展示PDF文件,同时也严重影响了许多基于Web和Electron的应用,这些应用(间接)使用PDF.js来提供预览功能。

如果您是处理PDF文件的JavaScript/Typescript应用程序的开发者,我们建议您检查您是否(间接)使用了PDF.js的易受攻击版本。查看本文末尾的缓解措施详情。

引言

PDF.js有两种常见的使用场景。首先,它是Firefox内置的PDF查看器。如果您使用Firefox,并且曾经下载或浏览过PDF文件,您就会看到它在起作用。其次,它被打包进一个名为pdfjs-dist的Node模块中,根据NPM的数据,每周下载量约为270万次。以这种形式,网站可以使用它来提供嵌入式PDF预览功能。从Git托管平台到笔记应用程序,现在您想到的任何应用程序很可能都在使用PDF.js。

PDF格式非常复杂,这是众所周知的。它支持各种媒体类型、复杂的字体渲染甚至基本的脚本功能,PDF阅读器是漏洞研究人员的常见目标。有如此大量的解析逻辑,难免会有一些错误,PDF.js也不例外。然而,它的独特之处在于它是用JavaScript而不是C或C++编写的。这意味着没有内存损坏问题的机会,但正如我们将看到的,它带来了自己的一套风险。

字形渲染

您可能会惊讶地听到,这个漏洞与PDF格式的(JavaScript!)脚本功能无关。相反,它是字体渲染代码中特定部分的一个疏忽。

PDF中的字体可以有几种不同的格式,其中一些对我们来说更加晦涩难懂。对于像TrueType这样的现代格式,PDF.js主要依赖于浏览器自己的字体渲染器。在其他情况下,它必须手动将字形(即字符)描述转换为页面上的曲线。为了优化性能,每个字形都会预先编译一个路径生成器函数。如果支持,这是通过创建一个JavaScript Function对象来完成的,该对象的主体(jsBuf)包含构成路径的指令:


// If we can, compile cmds into JS for MAXIMUM SPEED...
if (this.isEvalSupported && FeatureTest.isEvalSupported) {
  const jsBuf = [];
  for (const current of cmds) {
    const args = current.args !== undefined ? current.args.join(",") : "";
    jsBuf.push("c.", current.cmd, "(", args, ");n");
  }
  // eslint-disable-next-line no-new-func
  console.log(jsBuf.join(""));
  return (this.compiledGlyphs[character] = new Function(
    "c",
    "size",
    jsBuf.join("")
  ));
}

从一个攻击者的角度来看,这确实非常有趣:如果我们能够以某种方式控制进入Function体的cmds并插入我们自己的代码,那么当渲染这样的字形时,它就会被执行。

那么,让我们看看这个命令列表是如何生成的。追溯到CompiledFont类的逻辑,我们找到了compileGlyph(...)方法。这个方法用一些通用命令(save, transform, scalerestore)初始化cmds数组,并将填充实际渲染命令的任务委托给compileGlyphImpl(...)方法:


  compileGlyph(code, glyphId) {
    if (!code || code.length === 0 || code[0] === 14) {
      return NOOP;
    }

    let fontMatrix = this.fontMatrix;
    ...

    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;
  }

如果我们对PDF.js代码进行插桩,以记录生成的Function对象,我们会发现生成的代码确实包含了这些命令:

c.save();
c.transform(0.001,0,0,0.001,0,0);
c.scale(size,-size);
c.moveTo(0,0);
c.restore();

此时,我们可以审计字体解析代码以及由字形产生的各种命令和参数,比如quadraticCurveTobezierCurveTo,但所有这些看起来都是相当无害的,除了数字之外,没有能力控制任何其他东西。然而,事实证明,上面看到的transform命令要有趣得多:

cmd"transform"args: fontMatrix.slice() },

这个fontMatrix数组被复制(使用.slice())并插入到Function对象的主体中,用逗号连接。代码显然假设它是一个数字数组,但情况总是这样吗?如果这个数组中有任何字符串,它将被原字面插入,周围没有任何引号。因此,这在最好的情况下会破坏JavaScript语法,而在最坏的情况下会导致任意代码执行。但我们能否控制fontMatrix的内容到那种程度呢?

字体矩阵登场

fontMatrix的默认值是[0.001, 0, 0, 0.001, 0, 0],但通常由字体本身在其嵌入的元数据中设置为自定义矩阵。具体做法因字体格式而异。以下是一个Type1解析器的例子:

  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;
        ...
      }
      ...
    }
    ...
  }

这对我们来说并不是特别有趣。尽管Type1字体在其头部技术上包含任意Postscript代码,但没有一个理智的PDF阅读器会完全支持这一点,大多数只是尝试读取具有预期类型的预定义键值对。在这种情况下,当遇到FontMatrix键时,PDF.js只是读取一个数字数组。看来CFF解析器——用于其他几种字体格式——在这方面也是类似的。总的来说,我们似乎确实限于数字。

然而,事实证明,这个矩阵的来源不止一个。显然,也可以在字体外部指定自定义FontMatrix值,即在PDF中的元数据对象里!仔细查看PartialEvaluator.translateFont(...)方法,我们发现它从与字体相关的PDF字典中加载了各种属性,其中之一就是fontMatrix

  const properties = {
    type,
    name: fontName.name,
    subtype,
    file: fontFile,
    ...
    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,
    ...
  };

在PDF格式中,字体定义由几个对象组成。Font、它的FontDescriptor和实际的FontFile。例如,由对象1、2和3表示:

1 0 obj
<<
/Type /Font
/Subtype /Type1
/FontDescriptor 2 0 R
/BaseFont /FooBarFont
>>
endobj

2 0 obj
<<
/Type /FontDescriptor
/FontName /FooBarFont
/FontFile 3 0 R
/ItalicAngle 0
/Flags 4
>>
endobj

3 0 obj
<<
/Length 100
>>
...(实际的二进制字体数据)...
endobj

上述代码中引用的dict指的是Font对象。因此,我们应该能够像这样定义一个自定义的FontMatrix数组:

1 0 obj
<<
/Type /Font
/Subtype /Type1
/FontDescriptor 2 0 R
/BaseFont /FooBarFont
/FontMatrix [1 2 3 4 5 6] % <-----
>>
endobj

尝试这样做时,最初看起来似乎不起作用,因为生成的Function体中的transform操作仍然使用默认矩阵。然而,这是因为字体文件本身覆盖了这个值。幸运的是,当使用一个内部没有FontMatrix定义的Type1字体时,PDF指定的值权威的,因为fontMatrix值不会被覆盖。

现在我们可以从一个PDF对象控制这个数组,我们拥有了我们想要的所有灵活性,因为PDF支持的不仅仅是数字类型的原语。让我们尝试插入一个字符串类型的值而不是数字(在PDF中,字符串由括号分隔):

/FontMatrix [1 2 3 4 5 (foobar)]

的确,它被直接插入到Function体中!

c.save();
c.transform(1,2,3,4,5,foobar);
c.scale(size,-size);
c.moveTo(0,0);
c.restore();

利用与影响

现在,插入任意JavaScript代码只是一个正确处理语法的问题。这里有一个经典的例子,通过首先关闭c.transform(...)函数,并利用尾随括号来触发一个警告:

/FontMatrix [1 2 3 4 5 (0); alert('foobar')]

结果完全符合预期:

CVE-2024-4367 PDF.js RCE 分析(译)
CVE-2024-4367漏洞利用

CVE-2024-4367漏洞利用

你可以在这里找到一个概念验证PDF文件。它被设计成可以使用普通文本编辑器轻松适应。为了演示JavaScript运行的上下文,警告将显示window.origin的值。有趣的是,这不是你在URL栏中看到的file://路径(如果你已经下载了文件)。相反,PDF.js在resource://pdf.js的源下运行。这阻止了访问本地文件,但在其他方面稍微有些特权。例如,即使要“下载”任意的file://URL,也可以调用文件下载(通过对话框)。此外,打开的PDF文件的真实路径存储在window.PDFViewerApplication.url中,允许攻击者监视人们打开PDF文件,了解他们何时打开文件以及他们正在对文件做什么,以及文件在他们机器上的位置。

在嵌入PDF.js的应用中,影响可能更糟。如果没有适当的缓解措施(见下文),这基本上给攻击者提供了一个XSS 原语,用于包含PDF查看器的域。根据应用程序的不同,这可能导致数据泄露、以受害者的名义执行恶意操作,甚至可能完全接管账户。在没有适当沙箱JavaScript代码的Electron应用程序中,这个漏洞甚至导致本地代码执行(!)。我们发现至少有一个流行的Electron应用程序是这种情况

缓解措施

针对这个漏洞的最佳缓解措施是将PDF.js更新到4.2.67或更高版本。大多数包装库如react-pdf也已经发布了 修补过的版本。因为一些更高级别的PDF相关库静态嵌入PDF.js,我们建议递归检查你的node_modules文件夹,以确保没有名为pdf.js的文件。PDF.js的无头用例(例如,在服务器端从PDF中获取统计数据和数据)似乎没有受到影响,但我们没有进行彻底的测试。也建议进行更新。

此外,一个简单的解决方法是将PDF.js设置isEvalSupported设置为false。这将禁用易受攻击的代码路径。如果您有严格的内容安全策略(禁用evalFunction构造函数的使用),漏洞也无法到达。

时间线

  • 2024-04-26 – 向Mozilla披露漏洞
  • 2024-04-29 – PDF.js v4.2.67发布到NPM,修复了这个问题
  • 2024-05-14 – 发布了包含修复版本的PDF.js的Firefox 126、Firefox ESR 115.11和Thunderbird 115.11
  • 2024-05-20 – 发布这篇博文

- END -

原文始发于微信公众号(3072):CVE-2024-4367 PDF.js RCE 分析(译)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年5月22日21:38:24
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CVE-2024-4367 PDF.js RCE 分析(译)https://cn-sec.com/archives/2767103.html

发表评论

匿名网友 填写信息