在上一篇文章中,我们研究了如何保护移动Java代码 。 这样做的一种选择是在笼子或沙箱中运行代码。
这篇文章探讨了如何为Java应用程序设置这样的沙箱。
安全经理
Java中支持沙箱的安全性设施是java.lang.SecurityManager
。
默认情况下,Java在没有SecurityManager
情况下运行,因此您应在应用程序中添加代码以启用以下代码:
System.setSecurityManager(new SecurityManager());
您可以使用标准的SecurityManager
或后代。
SecurityManager
有许多checkXXX()
方法,这些方法都转发给checkPermission(permission, context)
。 此方法要求AccessController
进行实际工作(请参见下文)。
[ checkXXX()
方法是Java 1.1的遗物 。]
如果允许请求的访问,则checkPermission()
安静地返回。 如果被拒绝,则抛出java.lang.SecurityException
。
实现沙箱的代码应在执行敏感操作之前调用checkXXX
方法:
SecurityManager securityManager = System.getSecurityManager();
if (securityManager != null) {Permission permission = ...;securityManager.checkPermission(permission);
}
JRE包含许多地方的代码。
权限
权限表示对系统资源的访问。
为了允许这种访问,必须向尝试访问的代码显式授予相应的许可(请参见下文)。
权限源自java.security.Permission 。 它们有一个名称和一个可选的操作列表(以逗号分隔的字符串值形式)。
Java附带了许多预定义的权限,例如FilePermission 。 您还可以添加自己的权限 。
以下是read
文件/home/remon/thesis.pdf
的权限:
Permission readPermission = new java.io.FilePermission('/home/remon/thesis.pdf', 'read');
您可以通过授予代码权限AllPermission
来执行任何事情。 这与不使用SecurityManager
运行它具有相同的效果。
政策规定
使用策略 授予权限。 策略负责确定代码是否有权执行安全敏感的操作。
AccessController
查阅Policy
以查看是否授予了Permission
。
在任何给定时间只能使用一个Policy
对象。 应用程序代码可以将Policy
子类化以提供自定义实现 。
Policy
的默认实现使用配置文件加载授权。 有一个系统范围的策略文件和一个(可选)用户策略文件 。
您可以使用PolicyTool
程序创建其他策略配置文件。 每个配置文件都必须使用UTF-8编码。
默认情况下,根本不授予代码任何权限。 每个grant
语句都会添加一些权限。 授予的权限无法撤消。
以下策略片断授予代码,从起源/home/remon/code/
目录read
权限的文件/home/remon/thesis.pdf
:
grant codeBase 'file:/home/remon/code/-' {permission java.io.FilePermission '/home/remon/thesis.pdf','read';
};
请注意, codeBase
的部分是URL ,因此,即使在Windows系统上,也应始终使用正斜杠。
带尾随/
codeBase
匹配指定目录中的所有类文件(不是JAR文件)。 带有尾随/*
codeBase
匹配该目录中包含的所有文件(类和JAR文件)。 带有尾随/-
codeBase
匹配目录中的所有文件(类和JAR文件),并递归匹配该目录中包含的子目录中的所有文件。
对于Windows系统上文件权限中的路径,您需要使用双反斜杠( \\
),因为\
是转义字符:
grant codeBase 'file:/C:/Users/remon/code/-' {permission java.io.FilePermission'C:\\Users\\remon\\thesis.pdf', 'read';
};
为了获得更大的灵活性,您可以编写带有可变部分的赠款。 我们已经看到了codeBase
通配符。 您也可以替换系统属性 :
grant codeBase 'file:/${user.home}/code/-' {permission java.io.FilePermission'${user.home}${/}thesis.pdf', 'read';
};
注意
${/}
替换为系统的路径分隔符。 无需在 codeBase
,因为这是一个URL。
签名码
当然,我们应该确保我们使用的代码是签名的 ,以便我们知道它实际上是我们认为来自的人。
我们可以使用signedBy
子句在策略中测试签名:
keystore 'my.keystore';
grant signedBy 'signer.alias', codeBase ... {...
};
这一政策片段使用的密钥库别名为my.keystore
来查找公钥证书与别名signer.alias
。
然后,它验证执行代码已由与找到的证书中的公钥相对应的私钥签名。
只能有一个keystore
条目。
codeBase
和signedBy
子句的组合指定了ProtectionDomain
。 同一ProtectionDomain
中的所有类都具有相同的权限。
特权代码
每当尝试进行资源访问时,堆栈上的所有代码都必须具有该资源访问的权限,除非堆栈上的某些代码已标记为privileged 。
将代码标记为特权可使一段受信任的代码临时允许访问比直接调用它的代码更多的资源。 换句话说,安全系统将所有呼叫者视为来自发出特权呼叫的类的ProtectionDomain
的所有呼叫者,但仅在特权呼叫的持续时间内。
您可以通过在AccessController.doPrivileged()
调用中运行代码来使代码具有特权:
AccessController.doPrivileged(new PrivilegedAction() {public Object run() {// ...privileged code goes here...return null; }
});
组装沙箱
现在我们有了组装沙箱所需的所有零件:
- 安装
SecurityManager
- 签名应用程序罐
- 授予我们签名的所有代码
AllPermission
- 在移动代码可能调用的地方添加权限检查
- 权限检查
doPrivileged()
块后运行代码
我在GitHub上创建了一个简单的示例。
参考: 安全软件开发博客中来自我们JCG合作伙伴 Remon Sinnema的Java代码沙盒 。
翻译自: https://www.javacodegeeks.com/2012/11/sandboxing-java-code.html