自从Acrobat和PDF中添加了脚本功能以来,安全性一直是一个备受关注的问题。随着互联网的日益普及,情况变得越来越糟。垃圾邮件无处不在,黑客似乎每天都在想出新的方法来入侵我们的计算机。幸运的是,Acrobat JavaScript一直是一个沙盒环境,对本地文件系统的访问受到严格控制,某些类型的潜在危险操作被限制在一个相对安全的上下文中。但即使有这些限制,早期的系统仍然松散且有些临时性。这也带来了一个问题:有些情况下确实需要执行这些潜在危险的操作。我们该如何处理这些情况?
在Acrobat DC 中,Adobe通过"特权"(Privilege)的概念正式确立了 Acrobat JavaScript 的安全性,并引入了"可信函数"(Trusted Function)作为创建特权的一种方式。
什么是特权?
Acrobat JavaScript中的所有操作分为两类:特权操作和非特权操作。例如,表单上使用的标准操作是非特权的,如计算、隐藏和显示字段、重置表单和提交表单。这些都是标准、无害的操作。然而,像访问Identity对象这样的操作就是特权的。Identity对象包含用户的姓名、电子邮件和其他专有信息。这些信息不是您希望任何脚本都能访问的。
在Acrobat JavaScript参考中,任何标记为特权的对象、函数或属性都是潜在危险的,只能在特权上下文中运行。Acrobat JavaScript参考中的所有条目在条目顶部都有一个"快速栏"(或"权限栏",取决于文档版本)。Identity对象的快速栏如下所示。
第三列中的红色"S"表示此对象只能从特权上下文中访问,并且有一个注释更明确地解释了这一点。如果权限栏的第三列为空,则该对象、函数或属性被认为是安全的,可以在Acrobat或PDF的任何上下文中运行。
特定JavaScript对象、函数或属性是否具有特权完全由Adobe决定。他们有一个安全团队日夜工作,找出安全漏洞并加以修补。
什么是特权上下文?
在Acrobat和PDF中,有许多不同的位置可以使用脚本。其中一些位置在用户的系统上,一些在PDF内部。假设脚本在用户的系统上,那么用户知道它的存在。只有能够物理访问系统的人才能将脚本放置在用户的系统上。因此,这些脚本被认为是特权上下文。这些特权位置包括文件夹级脚本、批处理脚本和JavaScript控制台窗口。
相反,PDF中的脚本可以来自任何地方。如果没有特殊机制(如数字证书),就无法知道PDF的真正来源。因此,PDF中的脚本不可信。PDF内部的所有脚本位置(如表单字段)都被视为非特权。当然,也有例外。如果PDF已经过认证,并且数字证书在用户的系统上,并且证书已经针对PDF进行了验证,那么PDF中的脚本位置可以被视为特权。但这是一个罕见的例外。一般来说,PDF内部的所有脚本都被限制为无害的非特权操作。
有时,特别是在自动化脚本中,需要从非特权上下文中运行特权操作。为了克服这一障碍,Adobe创建了可信函数。
什么是可信函数?
可信函数对自动化脚本最有用。自动化脚本通常使用所谓的文件夹级脚本文件创建。这些JavaScript文件被放置在用户系统上的特殊Acrobat文件夹中。它们在Acrobat启动时加载并运行,并且只在Acrobat启动时运行。
一般的想法是,自动化脚本设置变量、数据和函数,这些将在以后用于某些特殊的自动化任务。通常,脚本还会设置某种用户界面项,如工具栏按钮或菜单项,以运行使用由文件夹级脚本创建的函数、变量和数据的脚本。
这里的问题是,虽然文件夹级脚本是特权的(因为它在用户的系统上),但用文件夹级脚本创建的函数、变量和数据不是特权的。例如,假设我想创建一个工具栏按钮,按下时替换PDF的最后一页。为此,我创建了一个文件夹级JavaScript文件,在其中放置了替换PDF页面的普通函数代码和创建调用替换页面函数的工具栏按钮的代码。
不幸的是,这不会起作用。替换页面的JavaScript命令是特权的,而在文件夹级脚本中创建的普通函数是非特权的。为了解决这个难题,我必须使用app.trustedFunction()将普通函数变成可信函数,如下所示。
// 页面替换函数
var ReplaceLastPage = app.trustedFunction(function(cPath) {
app.beginPriv();
this.replacePages(this.numPages-1, cPath);
app.endPriv();
});
让我们看看这段代码是如何工作的。app.trustedFunction()有一个输入,即一个普通函数。上面的代码使用了一个内联定义的无名函数。这只是设置可信函数的一种便捷方式。普通函数可以在其他地方定义为命名函数,然后使用函数名传递给app.trustedFunction()。app.trustedFunction()的返回值与传入的函数完全相同,但现在该函数具有特权,可以从任何执行上下文中调用,甚至是PDF内部的脚本。
可信函数不能在任何地方创建。它们只能在特权上下文中创建。但一旦创建,它们可以在任何地方使用。在上面的例子中,替换函数是在文件夹级脚本中创建的,该脚本在Acrobat启动时加载并运行,此时是特权的。
创建可信函数的最后一部分是注意app.beginPriv()和app.endPriv()代码行。这些行将函数中需要特权的部分括起来。它们必须存在才能使代码具有特权。
创建可信函数真的就这么简单。最重要的部分是它必须从已经具有特权的上下文中完成,并且函数内部的特权部分必须用app.beginPriv()和app.endPriv()函数括起来。
您可以在下面的链接中的Acrobat JavaScript指南和Acrobat JavaScript参考中找到关于特权、可信函数以及本文中使用的所有各种Acrobat JavaScript功能的更多信息。请查看"文档"选项卡。
扩展内容与示例代码
可信函数的工作原理
可信函数本质上是一个包装器,它将普通函数提升为可以在任何上下文中执行特权操作的函数。这种机制类似于操作系统中的权限提升(privilege escalation)。
完整示例代码
// 文件夹级脚本 (privileged-context.js)
// 这个脚本在Acrobat启动时自动加载
// 创建可信函数来访问用户身份信息
var GetUserInfo = app.trustedFunction(function() {
app.beginPriv();
try {
// 访问特权对象 - Identity
var identity = {
name: identity.name,
email: identity.email,
company: identity.company
};
app.endPriv();
return identity;
} catch(e) {
app.endPriv();
console.println("Error accessing identity: " + e);
return null;
}
});
// 创建工具栏按钮
app.addToolButton({
cName: "User Info",
cLabel: "显示用户信息",
cEnable: "event.rc= (event.target != null);",
cExec: "showUserInfo();"
});
// 普通函数 - 将被PDF中的脚本调用
function showUserInfo() {
var info = GetUserInfo();
if(info) {
app.alert("用户信息:\n姓名: " + info.name +
"\n邮箱: " + info.email +
"\n公司: " + info.company);
} else {
app.alert("无法获取用户信息");
}
}
代码备注
- app.beginPriv()/app.endPriv():这对函数必须成对出现,它们之间的代码块将获得特权执行权限
- 错误处理:在特权代码块中使用try-catch确保app.endPriv()始终被执行
- 工具栏按钮:演示了如何将可信函数与用户界面集成
- 身份信息访问:Identity对象是典型的特权对象,包含敏感用户数据
在PDF中使用可信函数
// PDF文档中的脚本 (非特权上下文)
// 尝试直接访问Identity对象会失败
try {
var name = identity.name; // 这将抛出安全异常
} catch(e) {
console.println("直接访问失败: " + e);
}
// 但可以调用之前创建的可信函数
if(typeof(GetUserInfo) == "function") {
var userData = GetUserInfo();
if(userData) {
this.getField("UserName").value = userData.name;
this.getField("UserEmail").value = userData.email;
}
}
安全模型图解
- 最小特权原则:只在必要时使用可信函数,且特权代码块应尽可能小
- 输入验证:可信函数应对所有输入参数进行严格验证
- 错误处理:妥善处理特权代码中的错误,确保始终调用app.endPriv()
- 文档记录:清晰记录哪些函数是可信的及其安全影响
- 代码签名:对包含可信函数的脚本进行数字签名
常见问题解答
Q: 可信函数会降低PDF的安全性吗?
A: 合理使用时不会。可信函数实际上提供了一种可控的特权提升机制,比早期版本中宽松的安全模型更安全。
Q: 可以在PDF内部创建可信函数吗?
A: 不可以。可信函数只能在特权上下文中创建,如文件夹级脚本、批处理脚本或控制台。
Q: 如何调试可信函数?
A: 可以使用JavaScript控制台(privileged context)进行调试,console.println()输出在控制台可见。
生词表
单词 | 词性 | 中文解释 |
---|---|---|
sandboxed | adj. | 沙盒化的,受限制的 |
ad hoc | adj. | 临时的,特别的 |
benign | adj. | 良性的,无害的 |
proprietary | adj. | 专有的,私有的 |
ubiquitous | adj. | 普遍存在的 |
converse | adj. | 相反的 |
validate | v. | 验证,确认 |
dilemma | n. | 困境,两难 |
bracket | v. | 用括号括起 |
automation | n. | 自动化 |
certificate | n. | 证书 |
explicitly | adv. | 明确地 |
mechanism | n. | 机制 |
console | n. | 控制台 |
inline | adj. | 内联的 |