网络安全

位置:IT落伍者 >> 网络安全 >> 浏览文章

探索Java应用程序的安全需求


发布日期:2019年10月02日
 
探索Java应用程序的安全需求

我们知道维护应用程序的安全并不简单不过我们也清楚完全可以采取一些措施来缓解安全缺陷带来的风险如果我们是网络工程师就可以将精力投入网络分区和包过滤器方面的知识如果用C语言编程我们就保护程序以防止缓沖区溢出如果用Java编程我们就考虑在安全管理器的保护之下运行应用程序每种情况下我们都用最佳实践中获得的知识来战胜无意造成的系统故障

Java应用程序的安全条款是本文所讨论内容的依据我们的讨论把重点放在Java安全管理器方面这是Java安全架构主题中的一个很小的子集

安全管理器是 javalangSecurityManager类或扩展自该类的一个类且它在运行时检查某些应用程序操作的权限一旦受到安全管理器的控制应用程序就只能执行那些由相关安全策略特别准许的操作默认情况下该策略是在纯文本 策略文件 中指定的所涉及的操作包括将文件写入特定目录写协同属性建立到特定主机的网络连接这里列出的只是其中的一小部分仅用一个简单的JVM命令行选项即可迫使Java应用程序在一个安全管理器下运行而且安全策略文件可以由任何文本编辑器轻松创建

虽然编辑这样一个安全策略文件并添加各种相关规则并不难不过获得策略制订权会更具有挑战性而且尽管没人可以为我们提供这样的策略制订权但工具能帮助我们理解那个策略应该是什么开发并使用这样的工具是我们即将着手的工作一旦我们有了它所发现的更宽泛更细粒度的策略就可以在开发产品运行时策略时将其作为起点或者为了更好地理解和认识应用程序的安全需求而研究它

本文的核心代码是一个通用的安全管理器要使用它就需要有Sun的JSE JVM 为了从特定的Java系统类中获得私有成员数据从安全管理器对Java Reflecition API的依赖性中派生JSE (Java Standard Edition)的需求由于缺乏对管理器运行所需的某有成员数据的公共访问因而需要使用Reflection API使用私有成员反射的后果就是管理器与其运行所在的的JVM内部紧密相关但这算不上什么严重的后果因为管理器是一个开发工具而不是产品组件一旦管理器提出一个策略制订起点我们就可以采用那个策略并在任何现代JVM上运行遵从它的应用程序

默认Java安全管理器

现代文献中所编写讨论及引用的绝大多数Java代码都代表着在不受安全管理器限制的情况下运行的应用程序因而这种应用程序对所有的机器资源包括磁盘网络和应用程序关闭等有完全访权限不过这种宽泛的访问权限很容易受到限制仅通过在JVM命令行上设置Djavasecuritymanager 选项就可以迫使应用程序在默认的Java安全管理器下运行

看一下下面这个简单的应用程序其目的是读取和打印用户的主目录

public class PrintHome {   public static void main(String[] argv) {      Systemoutprintln(SystemgetProperty(userhome));   }}

编译代码并让它在默认安全管理器的限制下运行

$ cd $HOME/Projects/CustomSecurityManager$ javac PrintHomejava$ java Djavasecuritymanager PrintHomeException in thread main javasecurityAccessControlException: access denied (javautilPropertyPermission userhome read)at PrintHomemain(PrintHomejava:)

上面的应用程序中未能获得并打印 userhome属性而且出于代码可读性方面的考虑我们省略了栈跟蹤中的绝大部分该应用程序未能执行是因为随着缺省安全策略运行的缺省安全管理器禁止访问userhome属性这个特权必须在运行策略文件中具体给出

创建一个包含单一规则的策略文件policytxt

grant codeBase file:/home/cid/Projects/CustomSecurityManager/{   permission javautilPropertyPermission userhome read;

重新运行该应用程序对策略文件的引用解决了对userhome的读访问问题

$ java Djavasecuritypolicy=policytxt Djavasecuritymanager PrintHome /home/cid

注意到我们通过设置系统属性javasecuritypolicy=policytxt来将JVM引用到策略文件中我们还假定PrintHome类位于/home/cid/Projects/CustomSecurityManager目录中policytxt 文件中的规则允许任何包含在那个目录中文件的代码去读取系统属性userhome结果规则允许PrintHome像预期的那样运行包含代码的文件就称其为代码库因此代码库就是一个类文件或jar文件

安全策略剖析工具ProfilingSecurityManager

正如我们前面提到的创建一个安全策略文件并不难即使像 策略池 这样的实用工具可提供协助而且在策略文件中允许有很强大的 语法快捷方式它允许创建有表现力的高效的规则使用这种高级规则符号我们可以指定比如说递归引用整个目录树的代码库URL这种递归 URL 规范不仅非常有用方便而且能够向代码阅读者屏蔽掉应用程序对资源需求的真实细粒度的深度这正是我们寻求的细节

因此我们的目标是双方面的首先我们希望在安全策略的限制下运行应用程序或者至少说我们希望确定该应用程序的安全需求其次我们需要一种程序性的方法来确定那些需求

带着这些目标我们来介绍定制的安全管理器secmgrProfilingSecurityManager这个类扩展了javalangSecurityManager但未实施本文至此所讨论的安全策略不过据说它会实施此类安全策略如果应用程序可以获得在运行时访问它所请求的一切内容的权利那么我们可接受这种说法将其转换成运行时安全策略的起点这样一来我们的两个目标就都可以实现

为了使用ProfilingSecurityManager首先编译并有策略地将其放在自己的jar文件中(源代码可以在 资源 这一节找到)单独将ProfilingSecurityManager放到它自己的jar文件中将允许我们过滤并取消由一些源于它自己jar文件代码库的活动所产生的输出规则ProfilingSecurityManager通过以下代码可以知道自己独有的代码库

if( urltoString()equals(thisCodeSourceURLString) ) {   return null;}

从而可以避免自身报告同时编译该工具并将其制成 jar

$ mkdir p classes lib$ rm rf classes/* lib/*$ javac d classes ProfilingSecurityManagerjava$ jar cf lib/psmjar C classes secmgr/manager$ rm rf classes/secmgr/manager

在我们继续讲下去之前应该讲一下如何激活ProfilingSecurityManager 使之成为应用程序安全管理器回想前面我们通过设置一个无对应属性值的系统属性Djavasecuritymanager来促使应用程序在缺省的Java安全管理器限制下运行我们需要进一步推广这种措施并通过给系统属性赋值将定制安全管理器指定为安全管理器Djavasecuritymanager=secmgrProfingSecurityManager因此被激活之后ProfilingSecurityManager将写入Systemout策略文件中需要的这些规则允许应用程序在无需抛出安全违反异常的情况下运行然而这些规则不能被处理成最终可用的形式直到应用程序在ProfilingSecurityManager下完成其运行为止为什么呢?因为我们知道仅在那时应用程序才完成了以检查资源为目的的请求访问所以为了使应用程序在ProfilingSecurityManager下完成运行时处理并整理这些规则我们提供了一个简单的Perl脚本parsecodebasepl(见 示例代码)以便以易于阅读的格式聚集格式化输出由代码库分类并组合的规则

好了我们现在用指定为安全管理器的ProfilingSecurityManager 和由parsecodebasepl处理的规则输出来运行简单的 PrintHome 程序会产生如下结果

$ java cp :lib/psmjar Djavasecuritymanager=secmgrProfilingSecurityManager

PrintHome  > rawout $ parsecodebasepl < rawout > policytxt$ cat policytxtgrant codeBase file:/home/cid/Projects/CustomSecurityManager/ {   permission javautilPropertyPermission userhome read;};$ java cp Djavasecuritymanager Djavasecuritypolicy=policytxt PrintHome /home/cid

我们再次看到ProfilingSecurityManager同时满足了我们的两个设计目标

    我们的应用程序使用定义良好特定于应用程序的策略在安全管理器下运行

    我们通过编程的方式确定了那个策略

ProfilingSecurityManager是如何工作的呢?ProfilingSecurityManager重写了javalangSecurityManager 的 checkPermission 方法的两个版本这个方法的两种形式是用于检查应用程序请求访问哪个资源或活动的中央枢纽被重写的checkPermission方法总是不需丢弃异常而返回——本意是允许访问—不过条件是它们必须建立并输出规则以允许为其操作负责的活动能首先得到调用

一个更复杂的例子剖析Tomcat Web应用程序

先将那个简单实例暂且搁置我们来观察ProfilingSecurityManager的一个复杂应用剖析Tomcat Web应用程序通过传送 security 选项到标准启动脚本Tomcat可在默认Java安全管理器下运行

$ $CATALINA_HOME/bin/startupsh security

传送 security 选项到startupsh 会导致对拥有同样security 选项的$CATALINA_HOME/bin/catalinash 的调用$CATALINA_HOME/bin/catalinash 实际上是调用Java来运行Tomcat 引导类orgapachecatalinastartupBootstrap 的一种脚本在这种情况下将进一步受到$CATALINA_HOME/conf/catalinapolicy中所规定默认策略的限制如果我们将调用停留在此处Tomcat将在由默认提供策略所限制的默认Java安全管理器下运行不过为了剖析Tomcat以及它可能包含的Web应用程序我们需要稍微多做一些工作为了用ProfilingSecurityManager剖析Web应用程序我们必须开发新的Tomcat启动脚本新启动脚本是一个临时设备而且仅用于剖析之后被废弃

为 $CATALINA_HOME/bin/catalinash做一个备份副本在脚本顶部附近的 $CATALINA_HOME/bin/catalinash 中近插入shell命令set x 然后启动Tomcat保存显示的shell执行命令到一个包含临时启动脚本的文件中停止Tomcat编辑临时脚本指定ProfilingSecurityManager 为安全管理器并修改类路径来定位它

以下是在Linux操作系统下的Tomcat 我们为使用 ProfilingSecurityManager进行编辑之前的临时启动脚本略经调整和格式化

#!/bin/shlog=$CATALINA_HOME/logs/catalinaout/java/jdk/jdk_/bin/java \Djavautilloggingmanager=orgapachejuliClassLoaderLogManager \Djnfigfile=/home/tomcat/tomcat/conf/loggingproperties \Djavaendorseddirs=/home/tomcat/tomcat/common/endorsed \classpath :/home/tomcat/tomcat/bin/bootstrapjar:\/home/tomcat/tomcat/bin/commonsloggingapijar \Djavasecuritymanager \Djavasecuritypolicy==/home/tomcat/tomcat/conf/catalinapolicy \Dcatalinabase=/home/tomcat/tomcat \Dcatalinahome=/home/tomcat/tomcat \Djavaiotmpdir=/home/tomcat/tomcat/temp \orgapachecatalinastartupBootstrap start >> $log \>& &

为了使用 ProfilingSecurityManager而进行编辑后该启动脚本如下所示

#!/bin/shlog=$CATALINA_HOME/logs/catalinaoutPATHTOPSM=$HOME/lib/psmjar  # make sure the profiler jar file is here/java/jdk/jdk_/bin/java \Djavautilloggingmanager=orgapachejuliClassLoaderLogManager \Djnfigfile=/home/tomcat/tomcat/conf/loggingproperties \Djavaendorseddirs=/home/tomcat/tomcat/common/endorsed \classpath $PATHTOPSM:/home/tomcat/tomcat/bin/bootstrapjar:\/home/tomcat/tomcat/bin/commonsloggingapijar \Djavasecuritymanager=secmgrmanagerProfilingSecurityManager \Djavasecuritypolicy==/home/tomcat/tomcat/conf/catalinapolicy \Dcatalinabase=/home/tomcat/tomcat \Dcatalinahome=/home/tomcat/tomcat \Djavaiotmpdir=/home/tomcat/tomcat/temp \orgapachecatalinastartupBootstrap start >> $log \>& &

这两个脚本的不同点在于新临时版本

    增加了类路径以指向ProfilingSecurityManager类的位置即$HOME/lib/psmjar 中

    Djavasecuritymanager参数中指定ProfilingSecurityManager为安全管理器

现在我们可以开始剖析了用临时启动脚本启动 Tomcat 促使相关web应用程序覆盖那些会在生产中被覆盖的代码以此考验它们的速度用代码覆盖Web应用程序是一个公认的难以实现的命令而且可能仅有部分可以实现停止Tomcat通过parsecodebasepl运行$CATALINA_HOME/logs/catalinaout 如下所示同时将处理过的规则保存到到 policytxt中

$ parsecodebasepl < $CATALINA_HOME/logs/catalinaout > policytxt

清单 处理由Tomcat和其中所包含的任意已执行Web应用程序生成的规则

注意 ProfilingSecurityManager只能为那些在剖析过程中执行的代码生成规则它会探测理论上可以在随机运行实例中触及的所有代码分支而不检查类文件中的字节码这样的字节码分析可能是将来的一个研究领域我们将加以补充但它不能替换由 ProfilingSecurityManager 做的运行时分析

对随Tomcat提供的策略文件 $CATALINA_HOME/conf/catalinapolicy 的检查表明Tomcat (Catalina)系统代码库获得了所有平台的权限事实上ProfilingSecurityManager会发现Tomcat系统类的这些相同规则但会以细粒度的方式指定它们ProfilingSecurityManager发现的关于Tomcat系统类的规则应从policytxt中手动删除

删除了 Tomcat系统规则后仍然保留在policytxt中的规则就是生产安全策略的起点这些规则表示Web应用程序的安全需求应仔细检查这些规则中的每一条以了解其用途并确保它与我们的应用程序目标相一致确信有好的草案策略时为$CATALINA_HOME/conf/catalinapolicy做一个备份副本并从 policytxt中将新草案规则整合进来然后用security 选项集回复到原来的 Tomcat 启动脚本继续测试

结束语

在Java安全管理器下运行应用程序可以增加代码的健壮性而且虽然获得安全策略制订权是一项艰巨的挑战不过这样做会让我们安心因为代码将在我们所规定的安全约束的限制下运行ProfilingSecurityManager可以为我们提供对应用程序所请求访问的资源集的充分可见度从而帮助我们获得策略制订权

资源

    示例代码 本文引用的示例代码包括 ProfilingSecurityManager 和 parsecodebasepl

    Subverting Java Access Protection for Unit Testing 主要讨论Java 映射基础

    Tomcat: The Definitive Guide 额外的一些Tomcat 安全性的详细介绍

    Java Permissions list

    Java 安全性架构文档

作者简介

Mark Petrovic是一位技术专家和软件开发人员

               

上一篇:让数据更安全 保护SQL Server的十个步骤

下一篇:设计不受传统网络限制的 P2P 系统