我们知道,维护应用程序的安全并不简单。不过我们也清楚,完全可以采取一些措施来缓解安全缺陷带来的风险。如果我们是网络工程师,就可以将精力投入网络分区和包过滤器方面的知识。如果用C语言编程,我们就保护程序以防止缓冲区溢出。如果用Java编程,我们就考虑在安全管理器的保护之下运行应用程序。每种情况下,我们都用最佳实践中获得的知识来战胜无意造成的系统故障。
Java应用程序的安全条款,是本文所讨论内容的依据。我们的讨论把重点放在Java安全管理器方面,这是Java安全架构主题中的一个很小的子集。
安全管理器是 java.lang.SecurityManager类或扩展自该类的一个类,且它在运行时检查某些应用程序操作的权限。一旦受到安全管理器的控制,应用程序就只能执行那些由相关安全策略特别准许的操作。默认情况下,该策略是在纯文本 策略文件 中指定的。所涉及的操作包括:将文件写入特定目录、写协同属性、建立到特定主机的网络连接,这里列出的只是其中的一小部分。仅用一个简单的JVM命令行选项即可迫使Java应用程序在一个安全管理器下运行,而且安全策略文件可以由任何文本编辑器轻松创建。
虽然编辑这样一个安全策略文件并添加各种相关规则并不难,不过获得策略制订权会更具有挑战性。而且尽管没人可以为我们提供这样的策略制订权,但工具能帮助我们理解那个策略应该是什么。开发并使用这样的工具是我们即将着手的工作。一旦我们有了它所发现的更宽泛、更细粒度的策略,就可以在开发产品运行时策略时将其作为起点,或者为了更好地理解和认识应用程序的安全需求而研究它。
本文的核心代码是一个通用的安全管理器,要使用它就需要有Sun的JSE 5 JVM。 为了从特定的Java系统类中获得私有成员数据,从安全管理器对Java Reflecition API的依赖性中派生JSE 5(Java Standard Edition)的需求。由于缺乏对管理器运行所需的某有成员数据的公共访问,因而需要使用Reflection API。使用私有成员反射的后果就是管理器与其运行所在的的JVM内部紧密相关。但这算不上什么严重的后果,因为管理器是一个开发工具,而不是产品组件。一旦管理器提出一个策略制订起点,我们就可以采用那个策略,并在任何现代JVM上运行遵从它的应用程序。
默认Java安全管理器
现代文献中所编写、讨论及引用的绝大多数Java代码都代表着在不受安全管理器限制的情况下运行的应用程序。因而这种应用程序对所有的机器资源包括磁盘、网络和应用程序关闭等有完全访权限。不过这种宽泛的访问权限很容易受到限制。仅通过在JVM命令行上设置Djava.security.manager 选项,就可以迫使应用程序在默认的Java安全管理器下运行。
看一下下面这个简单的应用程序,其目的是读取和打印用户的主目录
public class PrintHome { public static void main(String[] argv) { System.out.println(System.getProperty("user.home")); } } |
编译代码,并让它在默认安全管理器的限制下运行
$ cd $HOME/Projects/CustomSecurityManager $ javac PrintHome.java $ java -Djava.security.manager PrintHome Exception in thread "main" java.security.AccessControlException: access denied (java.util.PropertyPermission user.home read) ... at PrintHome.main(PrintHome.java:5) |
上面的应用程序中未能获得并打印 user.home属性,而且出于代码可读性方面的考虑,我们省略了栈跟踪中的绝大部分。该应用程序未能执行是因为随着缺省安全策略运行的缺省安全管理器禁止访问user.home属性。这个特权必须在运行策略文件中具体给出。
创建一个包含单一规则的策略文件policy.txt
grant codeBase "file:/home/cid/Projects/CustomSecurityManager/"{ permission java.util.PropertyPermission "user.home", "read"; |
重新运行该应用程序,对策略文件的引用解决了对user.home的读访问问题
$ java -Djava.security.policy=policy.txt -Djava.security.manager PrintHome /home/cid |
注意到,我们通过设置系统属性java.security.policy=policy.txt来将JVM引用到策略文件中。我们还假定PrintHome类位于/home/cid/Projects/CustomSecurityManager目录中。policy.txt 文件中的规则允许任何包含在那个目录中文件的代码去读取系统属性user.home。结果,规则允许PrintHome像预期的那样运行。包含代码的文件就称其为代码库。因此代码库就是一个类文件或jar文件
安全策略剖析工具:ProfilingSecurityManager
正如我们前面提到的,创建一个安全策略文件并不难,即使像 策略池 这样的实用工具可提供协助。而且在策略文件中允许有很强大的 语法快捷方式,它允许创建有表现力的、高效的规则。使用这种高级规则符号,我们可以指定,比如说,递归引用整个目录树的代码库URL。这种递归 URL 规范不仅非常有用、方便,而且能够向代码阅读者屏蔽掉应用程序对资源需求的真实、细粒度的深度。这正是我们寻求的细节。
因此我们的目标是双方面的:首先,我们希望在安全策略的限制下运行应用程序,或者至少说,我们希望确定该应用程序的安全需求。其次,我们需要一种程序性的方法来确定那些需求。
带着这些目标,我们来介绍定制的安全管理器secmgr.ProfilingSecurityManager。这个类扩展了java.lang.SecurityManager,但未实施本文至此所讨论的安全策略。不过,据说它会实施此类安全策略如果应用程序可以获得在运行时访问它所请求的一切内容的权利。那么我们可接受这种说法,将其转换成运行时安全策略的起点。这样一来,我们的两个目标就都可以实现。
为了使用ProfilingSecurityManager,首先编译并有策略地将其放在自己的jar文件中(源代码可以在 资源 这一节找到)。单独将ProfilingSecurityManager放到它自己的jar文件中将允许我们过滤并取消由一些源于它自己jar文件代码库的活动所产生的输出规则。ProfilingSecurityManager通过以下代码可以知道自己独有的代码库,
if( url.toString().equals(thisCodeSourceURLString) ) { return null; } |
从而可以避免自身报告。同时编译该工具,并将其制成 jar:
$ mkdir -p classes lib $ rm -rf classes/* lib/* $ javac -d classes ProfilingSecurityManager.java $ jar cf lib/psm.jar -C classes secmgr/manager $ rm -rf classes/secmgr/manager |
在我们继续讲下去之前,应该讲一下如何激活ProfilingSecurityManager ,使之成为应用程序安全管理器。回想前面,我们通过设置一个无对应属性值的系统属性Djava.security.manager来促使应用程序在缺省的Java安全管理器限制下运行。我们需要进一步推广这种措施,并通过给系统属性赋值将定制安全管理器指定为安全管理器:Djava.security.manager=secmgr.ProfingSecurityManager。因此被激活之后,ProfilingSecurityManager将写入System.out,策略文件中需要的这些规则允许应用程序在无需抛出安全违反异常的情况下运行。然而,这些规则不能被处理成最终可用的形式,直到应用程序在ProfilingSecurityManager下完成其运行为止。为什么呢?因为我们知道仅在那时应用程序才完成了以检查资源为目的的请求访问。所以为了使应用程序在ProfilingSecurityManager下完成运行时处理并整理这些规则,我们提供了一个简单的Perl脚本parsecodebase.pl(见 示例代码),以便以易于阅读的格式聚集、格式化、输出由代码库分类并组合的规则。
好了,我们现在用指定为安全管理器的ProfilingSecurityManager 和由parsecodebase.pl处理的规则输出来运行简单的 PrintHome 程序,会产生如下结果:
$ java -cp .:lib/psm.jar -Djava.security.manager=secmgr.ProfilingSecurityManager PrintHome > raw.out $ parsecodebase.pl < raw.out > policy.txt $ cat policy.txt grant codeBase "file:/home/cid/Projects/CustomSecurityManager/" { permission java.util.PropertyPermission "user.home", "read"; }; $ java -cp . -Djava.security.manager -Djava.security.policy=policy.txt PrintHome /home/cid |
我们再次看到,ProfilingSecurityManager同时满足了我们的两个设计目标:
- 我们的应用程序使用定义良好、特定于应用程序的策略在安全管理器下运行
- 我们通过编程的方式确定了那个策略
ProfilingSecurityManager是如何工作的呢?ProfilingSecurityManager重写了java.lang.SecurityManager 的 checkPermission 方法的两个版本。这个方法的两种形式是用于检查应用程序请求访问哪个资源或活动的中央枢纽。被重写的checkPermission方法总是不需丢弃异常而返回——本意是“允许访问”—不过条件是它们必须建立并输出规则,以允许为其操作负责的活动能首先得到调用。
一个更复杂的例子:剖析Tomcat Web应用程序
先将那个简单实例暂且搁置,我们来观察ProfilingSecurityManager的一个复杂应用:剖析Tomcat Web应用程序。通过传送 -security 选项到标准启动脚本,Tomcat可在默认Java安全管理器下运行:
$ $CATALINA_HOME/bin/startup.sh -security |
传送 -security 选项到startup.sh 会导致对拥有同样-security 选项的$CATALINA_HOME/bin/catalina.sh 的调用。$CATALINA_HOME/bin/catalina.sh 实际上是调用Java来运行Tomcat 引导类org.apache.catalina.startup.Bootstrap 的一种脚本,在这种情况下,将进一步受到$CATALINA_HOME/conf/catalina.policy中所规定默认策略的限制。如果我们将调用停留在此处,Tomcat将在由默认提供策略所限制的默认Java安全管理器下运行。不过为了剖析Tomcat以及它可能包含的Web应用程序,我们需要稍微多做一些工作。为了用ProfilingSecurityManager剖析Web应用程序,我们必须开发新的Tomcat启动脚本。新启动脚本是一个临时设备,而且仅用于剖析,之后被废弃。
为 $CATALINA_HOME/bin/catalina.sh做一个备份副本。在脚本顶部附近的 $CATALINA_HOME/bin/catalina.sh 中近插入shell命令set -x ,然后启动Tomcat。保存显示的shell执行命令到一个包含临时启动脚本的文件中。停止Tomcat,编辑临时脚本,指定ProfilingSecurityManager 为安全管理器并修改类路径来定位它。
以下是在Linux操作系统下的Tomcat 5.5.17中,我们为使用 ProfilingSecurityManager进行编辑之前的临时启动脚本,略经调整和格式化。
#!/bin/sh log=$CATALINA_HOME/logs/catalina.out /java/jdk/jdk1.5.0_06/bin/java / -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager / -Djava.util.logging.config.file=/home/tomcat/tomcat/conf/logging.properties / -Djava.endorsed.dirs=/home/tomcat/tomcat/common/endorsed / -classpath :/home/tomcat/tomcat/bin/bootstrap.jar:/ /home/tomcat/tomcat/bin/commons-logging-api.jar / -Djava.security.manager / -Djava.security.policy==/home/tomcat/tomcat/conf/catalina.policy / -Dcatalina.base=/home/tomcat/tomcat / -Dcatalina.home=/home/tomcat/tomcat / -Djava.io.tmpdir=/home/tomcat/tomcat/temp / org.apache.catalina.startup.Bootstrap start >> $log / 2>&1 & |
为了使用 ProfilingSecurityManager而进行编辑后,该启动脚本如下所示:
#!/bin/sh log=$CATALINA_HOME/logs/catalina.out PATHTOPSM=$HOME/lib/psm.jar # make sure the profiler jar file is here /java/jdk/jdk1.5.0_06/bin/java / -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager / -Djava.util.logging.config.file=/home/tomcat/tomcat/conf/logging.properties / -Djava.endorsed.dirs=/home/tomcat/tomcat/common/endorsed / -classpath $PATHTOPSM:/home/tomcat/tomcat/bin/bootstrap.jar:/ /home/tomcat/tomcat/bin/commons-logging-api.jar / -Djava.security.manager=secmgr.manager.ProfilingSecurityManager / -Djava.security.policy==/home/tomcat/tomcat/conf/catalina.policy / -Dcatalina.base=/home/tomcat/tomcat / -Dcatalina.home=/home/tomcat/tomcat / -Djava.io.tmpdir=/home/tomcat/tomcat/temp / org.apache.catalina.startup.Bootstrap start >> $log / 2>&1 & |
这两个脚本的不同点在于新临时版本:
- 增加了类路径以指向ProfilingSecurityManager类的位置,即$HOME/lib/psm.jar 中
- 在-Djava.security.manager参数中指定ProfilingSecurityManager为安全管理器
现在我们可以开始剖析了。用临时启动脚本启动 Tomcat 。促使相关web应用程序覆盖那些会在生产中被覆盖的代码,以此考验它们的速度。用代码覆盖Web应用程序是一个公认的、难以实现的命令,而且可能仅有部分可以实现。停止Tomcat,通过parsecodebase.pl运行$CATALINA_HOME/logs/catalina.out ,如下所示,同时将处理过的规则保存到到 policy.txt中。
$ parsecodebase.pl < $CATALINA_HOME/logs/catalina.out > policy.txt |
清单 9. 处理由Tomcat和其中所包含的任意已执行Web应用程序生成的规则
注意 ProfilingSecurityManager只能为那些在剖析过程中执行的代码生成规则。它会探测理论上可以在随机运行实例中触及的所有代码分支,而不检查类文件中的字节码。这样的字节码分析可能是将来的一个研究领域,我们将加以补充,但它不能替换由 ProfilingSecurityManager 做的运行时分析。
对随Tomcat提供的策略文件 $CATALINA_HOME/conf/catalina.policy 的检查表明,Tomcat ("Catalina")系统代码库获得了所有平台的权限。事实上,ProfilingSecurityManager会发现Tomcat系统类的这些相同规则,但会以细粒度的方式指定它们。ProfilingSecurityManager发现的关于Tomcat系统类的规则应从policy.txt中手动删除。
删除了 Tomcat系统规则后,仍然保留在policy.txt中的规则就是生产安全策略的起点。这些规则表示Web应用程序的安全需求。应仔细检查这些规则中的每一条,以了解其用途,并确保它与我们的应用程序目标相一致。确信有好的草案策略时,为$CATALINA_HOME/conf/catalina.policy做一个备份副本,并从 policy.txt中将新草案规则整合进来。然后用-security 选项集回复到原来的 Tomcat 启动脚本,继续测试。
结束语
在Java安全管理器下运行应用程序可以增加代码的健壮性。而且,虽然获得安全策略制订权是一项艰巨的挑战,不过这样做会让我们安心,因为代码将在我们所规定的安全约束的限制下运行。ProfilingSecurityManager可以为我们提供对应用程序所请求访问的资源集的充分可见度,从而帮助我们获得策略制订权。
来源:http://searchwebservices.techtarget.com.cn/tips/25/3262525.shtml