《Java编码指南:编写安全可靠程序的75条建议》—— 指南20:使用安全管理器创建一个安全的沙盒-阿里云开发者社区

开发者社区> 开发与运维> 正文

《Java编码指南:编写安全可靠程序的75条建议》—— 指南20:使用安全管理器创建一个安全的沙盒

简介: 安全管理器是一个类,它允许应用程序实现一个安全策略。在执行一个可能不安全或敏感的操作前,它允许应用程序确定这个操作是什么,它是否正在一个安全的允许执行这个操作的上下文中进行尝试。该应用程序可以允许或禁止该操作。

本节书摘来异步社区《Java编码指南:编写安全可靠程序的75条建议》一书中的第1章,第1.20节,作者:【美】Fred Long(弗雷德•朗), Dhruv Mohindra(德鲁•莫欣达), Robert C.Seacord(罗伯特 C.西科德), Dean F.Sutherland(迪恩 F.萨瑟兰), David Svoboda(大卫•斯沃博达),更多章节内容可以访问云栖社区“异步社区”公众号查看。

指南20:使用安全管理器创建一个安全的沙盒

根据Java API中SecurityManager类的文档[API 2013]:

安全管理器是一个类,它允许应用程序实现一个安全策略。在执行一个可能不安全或敏感的操作前,它允许应用程序确定这个操作是什么,它是否正在一个安全的允许执行这个操作的上下文中进行尝试。该应用程序可以允许或禁止该操作。

安全管理器可以与任何Java代码相关联。

applet的安全管理器拒绝applet最基本特权以外的所有其他特权。如此设计旨在防止无意的系统修改、信息泄漏和用户冒名。安全管理器的使用并不局限于客户端保护。比如Tomcat和WebSphere这样的Web服务器,使用安全管理器来隔离木马servlet和恶意的Java服务器页面(JSP),保护敏感的系统资源不被无意访问。

从命令行运行的Java应用程序,可以通过命令行参数标志来设置默认的安全管理器或者自定义的安全管理器。另外,可以通过编程方式安装一个安全管理器。以编程方式安装安全管理器能促使程序创建一个默认的沙盒,这个沙盒会基于当前生效的安全策略来允许或拒绝敏感动作。

从Java 2 SE平台开始,SecurityManager不再是一个抽象类。因此,不再需要显式覆盖它的方法。以编程方式创建和使用安全管理器时,代码必须具有运行时权限来调用createSecurityManager(实例化SecurityManager)和setSecurityManager(安装它)。这些权限只在安全管理器已经安装时才被检查。在有些情况下,这很有用,比如在一个虚拟主机上有一个默认的安全管理器,它必须拒绝个人主机以自定义的安全管理器来覆盖默认的安全管理器。

安全管理器与AccessController类密切相关,前者是访问控制的中心,后者提供了访问控制算法的实际实现。安全管理器支持以下两项。

提供向后兼容性:老程序通常包含自定义安全性管理器类的实现,因为它最初是抽象类。
定义自定义策略:通过子类化安全管理器来允许定义自定义的安全策略(如多层次、粗粒度或细粒度)。
关于自定义安全管理器与默认安全管理器的实现和使用,Java安全架构规范(Java security architecture specification) [SecuritySpec 2010]中是这么声明的:

我们鼓励在应用程序代码中使用AccessController类,而定制安全管理器(通过子类化)应该是最后的手段,应当十分小心。此外,一个定制的安全管理器,如在调用标准安全检查前总是检查当前时间,在合适的时候可以而且应该使用AccessController所提供的算法。

许多Java SE API在执行敏感操作前,都会默认执行安全管理器检查。例如,java.io.FileInputStream的构造函数,如果调用者没有足够的读取文件的权限,它就会抛出SecurityException异常。因为SecurityException安全异常是RuntimeException运行时异常的一个子类,所以一些API方法(如java.io.FileReader类的方法)的声明可能缺乏列出SecurityException的throws语句。要避免依赖于没有在API方法文档中指定的安全管理器检查是否存在。

违规代码示例(命令行安装)

下面的违规代码示例没有从命令行安装任何安全管理器。因此,程序运行时启用了所有权限,也就是说,没有安全管理器来防止程序可能执行的任何邪恶动作。

java LocalJavaApp```
合规解决方案(默认策略文件)
任何Java程序都可以尝试以编程方式安装SecurityManager,尽管当前活动的安全管理器可能禁止此操作。被设计为本地运行的应用程序可以在调用时通过命令行参数指定一个默认的安全管理器。

当必须禁止应用程序以编程方式安装定制安全管理器,并且在任何情况下都要遵守默认的安全策略时,使用命令行选项更好。下面的合规解决方案使用了合适的命令行参数来安装默认安全管理器。安全策略文件为应用程序的预期动作授予了权限。

java -Djava.security.manager -Djava.security.policy=policyURL \
   LocalJavaApp`
命令行参数标志可以指定一个自定义的安全管理器,其策略被全局执行。使用-Djava.security.manager标志,如下所示:

java -Djava.security.manager=my.security.CustomManager ...```
如果由当前安全管理器执行的当前安全策略禁止替换(通过省略Runtime Permission("setSecurityManager")),那么任何试图调用setSecurity Manager()的方法都会抛出SecurityException异常。

在类Unix系统及其等效的微软Windows系统的/path/to/java.home/ lib/security目录中,可以找到默认的安全策略文件java.sercurity,它负责许多权限(读取系统属性、绑定到未授权端口等)的授予。一个特定于用户的策略文件也可能位于该用户的主目录中。这些策略文件的集合用来指定为程序授予的权限。java.security文件可以指定使用的策略文件。如果系统级的java.policy文件或java.security文件其中之一被删除,那么就没有用来执行Java程序的权限。

####合规解决方案(定制策略文件)
当以一个定制的策略文件覆盖全局Java安全策略时,要使用双等号(==)替换单等号(=):

java -Djava.security.manager \
   -Djava.security.policy==policyURL \
   LocalJavaApp`

合规解决方案(附加策略文件)

appletviewer自动安装了一个带有标准策略文件的安全管理器,并且使用-J标志指定了附加的策略文件。

appletviewer -J-Djava.security.manager \
       -J-Djava.security.policy==policyURL LocalJavaApp```
注意,当安全属性文件(java.security)中的policy.allowSystemProperty属性值被设置为false时,参数中指定的策略文件就会被忽略;该属性的默认值为true。默认策略实现和策略文件语法(Default Policy Implementation and Policy File Syntax)[Policy 2010]深入讨论了编写策略文件时的问题和语法。

####违规代码示例(编程式安装)
也可以使用静态的System.setSecurityManager()方法来激活SecurityManager。同时只能有一个SecurityManager处于激活状态。这个方法以参数中提供的SecurityManager或null来取代当前活动的SecurityManager。

下面的违规代码示例在让当前安全管理器失效的同时,并没有在原来位置上安装另一个安全管理器。因此,后续代码将在启用所有权限的状态下运行,对程序可能执行的任何邪恶动作都没有了限制。

try {
 System.setSecurityManager(null);
} catch (SecurityException se) {
 // Cannot set security manager, log to file
}`
一个实施合理安全策略的活动的SecurityManager能阻止系统令其失效,让这段代码抛出SecurityException异常。

合规解决方案(默认安全管理器)

下面的合规解决方案对默认安全管理器进行了实例化和设置。

try {
 System.setSecurityManager(new SecurityManager());
} catch (SecurityException se) {
 // Cannot set security manager, log appropriately
}```
####合规解决方案(自定义安全管理器)
下面的合规解决方案演示了如何实例化一个名为CustomSecurityManager的自定义安全管理器。程序首先通过密码调用了它的构造函数,然后将这个自定义安全管理器安装成了活动的安全管理器。

char password[] = / 初始化 /
try {
 System.setSecurityManager(
  new CustomSecurityManager("password here")
 );
} catch (SecurityException se) {
 // Cannot set security manager, log appropriately
}`
这段代码执行后,执行安全检查的API将使用这个自定义安全管理器。如前所述,只有当默认安全管理器缺少所需要的功能时,才考虑安装自定义安全管理器。

适用性

Java的安全性从根本上取决于安全管理器的存在。当其不存在时,敏感动作可以无限制地执行。

在运行时编程检测安全管理器的存在与否是很简单的。静态分析可以解决此类代码的存在与否,如果该代码被执行,它将试图安装一个安全管理器。在某些情况下,可以检查安全管理器是否安装得够早、是否指定了所需的属性,或者是否可以保证会被安装,但通常情况下是不可判定的。

当已知默认的全局安全管理器总是会从命令行安装时,对setSecurity Manager()方法的调用在受控环境中可能会被忽略。这很难实施,如果环境配置不正确,会出现漏洞。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章