在介绍Unsafe文中我们提到了AccessController,它有什么作用呢?本节我们会对其进行介绍,在介绍它之前我们先介绍一下java的安全模型
java安全模型
java中将执行程序分成本地和远程两种,本地代码默认是可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的java实现中,安全依赖于沙箱机制。沙箱机制就是将java代码限定在jvm特定的运行范围,并且严格限制代码对本地系统的资源访问,通过这样的措施保证对远程代码的有效隔离,防止对本地系统造成破坏。但是,如此严格的安全机制也给程序的功能扩展带来障碍,比如远程用户希望远程代码访问本地系统文件时,就无法实现了。所以java在后续版本中,对安全机制做了改进,增加安全策略,允许用户指定代码对本地资源的访问权限。经过多个jdk版本的升级不断改进安全机制。
最新的安全机制增加了域的概念。jvm会把所有代码加载到不同系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则通过系统域的部分代理来对各种需要的资源进行访问。jvm中不同的受保护域,对应不一样的权限。存在于不同区域的类文件就具有了当前域的全部权限。
在实际应用开发过程中,应用程序可能无法直接访问某些系统资源,但应用程序必须得到这些资源才能够完成功能,这时候最常用到的 API 就是 doPrivileged。doPrivileged 方法能够使一段受信任代码获得更大的权限,甚至比调用它的应用程序还要多,可做到临时访问更多的资源。
什么是沙箱
Java安全模型的核心就是Java沙箱(sandbox),沙箱是一个限制程序运行的环境。限制程序运行一方面是为了保护系统资源,同时另一方面也为了保护程序自己。沙箱主要限制系统资源访问,那系统资源包括:CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。
所有的Java程序运行都可以指定沙箱,可以定制安全策略。而一个Java程序运行的安全策略,包括了以下几点基础:
- 字节码校验器(bytecode verifier):确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但是,不是所有的类文件都会经过字节码校验,比如核心类。
- 类加载器(class loader):所有的Java类都是通过类加载器加载的,可以自定义类加载器来设置加载类的权限。
- 存取控制器(access controller):存取控制器可以控制核心API对操作系统的存取权限,而这个控制的策略设定,可以由用户指定。
- 安全管理器(security manager):是核心API和操作系统之间的主要接口。实现权限控制,比存取控制器优先级高。
- 安全软件包(security package):java.security下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括: 安全提供者、消息摘要、数字签名、加密、鉴别。
沙箱包含的要素:
1. 权限:是指允许代码执行的操作。包含三部分:权限类型、权限名和允许的操作。权限类型是实现了权限的Java类名,是必需的。权限名一般就是对哪类资源进行操作的资源定位(比如一个文件名或者通配符、网络主机等),一般基于权限类型来设置,有的比如java.security.AllPermission不需要权限名。允许的操作也和权限类型对应,指定了对目标可以执行的操作行为,比如读、写等。如下面的例子:
permission java.security.AllPermission; //权限类型
permission java.lang.RuntimePermission "stopThread"; //权限类型+权限名
permission java.io.FilePermission "/tmp/foo" "read"; //权限类型+权限名+允许的操作
2. 代码源:是类所在的位置,表示为以URL地址。
3. 保护域:用来组合代码源和权限,这是沙箱的基本概念。保护域就在于声明了比如由代码A可以做权限B这样的事情。
4. 策略文件:是控制沙箱的管理要素,一个策略文件包含一个或多个保护域的项。策略文件完成了代码权限的指定任务,策略文件包括全局和用户专属两种。
为了管理沙箱,我认为策略文件是最重要的内容。JVM可以使用多个策略文件,不过一般两个最常用。一个是全局的:作用于JVM的所有实例。另一个是用户自己的,可以存储到用户的主目录下。
默认的策略文件我们先参考一下:
// Standard extensions get all permissions by default
grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};
// default permissions granted to all domains
grant {
// Allows any thread to stop itself using the java.lang.Thread.stop()
// method that takes no argument.
// Note that this permission is granted by default only to remain
// backwards compatible.
// It is strongly recommended that you either remove this permission
// from this policy file or further restrict it to code sources
// that you specify, because Thread.stop() is potentially unsafe.
// See the API specification of java.lang.Thread.stop() for more
// information.
permission java.lang.RuntimePermission "stopThread";
// allows anyone to listen on dynamic ports
permission java.net.SocketPermission "localhost:0", "listen";
// permission for standard RMI registry port
permission java.net.SocketPermission "localhost:1099", "listen";
// "standard" properies that can be read by anyone
permission java.util.PropertyPermission "java.version", "read";
permission java.util.PropertyPermission "java.vendor", "read";
permission java.util.PropertyPermission "java.vendor.url", "read";
permission java.util.PropertyPermission "java.class.version", "read";
permission java.util.PropertyPermission "os.name", "read";
permission java.util.PropertyPermission "os.version", "read";
permission java.util.PropertyPermission "os.arch", "read";
permission java.util.PropertyPermission "file.separator", "read";
permission java.util.PropertyPermission "path.separator", "read";
permission java.util.PropertyPermission "line.separator", "read";
permission java.util.PropertyPermission "java.specification.version", "read";
permission java.util.PropertyPermission "java.specification.vendor", "read";
permission java.util.PropertyPermission "java.specification.name", "read";
permission java.util.PropertyPermission "java.vm.specification.version", "read";
permission java.util.PropertyPermission "java.vm.specification.vendor", "read";
permission java.util.PropertyPermission "java.vm.specification.name", "read";
permission java.util.PropertyPermission "java.vm.version", "read";
permission java.util.PropertyPermission "java.vm.vendor", "read";
permission java.util.PropertyPermission "java.vm.name", "read";
};
policy.url.*这个属性指明了使用的策略文件,如上文所述,默认的两个位置就在这里配置,用户可以自行更改顺序和存储位置。而policy.allowSystemProperty指明是否允许用户自行通过命令行指定policy文件。
5. 密钥库:保存密钥证书的地方。
默认沙箱
通过Java命令行启动的Java应用程序,默认不启用沙箱。要想启用沙箱,启动命令需要做如下形式的变更:
java -Djava.security.manager
也可以通过编码的方式启动
System.setSecurityManager(new SecurityManager());
通过参数启动,本质也是通过编码启动,只不过参数启动更灵活。
沙箱启动后,安全管理器会使用两个默认的策略文件来确定沙箱启动参数。当然也可以通过命令指定:
java -Djava.security.policy=<URL>
如果要求启动时只遵循一个策略文件,那么启动参数要加个等号,如下:
java -Djava.security.policy==<URL>
举例:
读文件:
public class SecurityTest {
public static void readFile() {
File f = new File("D:\\test.txt");
try(InputStream in = new FileInputStream(f)) {
byte[] content = new byte[1024];
while (in.read(content) != -1) {
System.out.println(new String(content));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
readFile();
}
}
发现输出是test。
接下来修改java启动参数,加入**-Djava.security.manager**,启动了安全沙箱。再运行,输出变成了异常
Exception in thread "main" java.security.AccessControlException: access denied ("java.io.FilePermission" "D:\test.txt" "read")
at java.security.AccessControlContext.checkPermission(Unknown Source)
at java.security.AccessController.checkPermission(Unknown Source)
at java.lang.SecurityManager.checkPermission(Unknown Source)
at java.lang.SecurityManager.checkRead(Unknown Source)
at java.io.FileInputStream.(Unknown Source)
at com.pch.security.SecurityTest.main(SecurityTest.java:14)
这里已经提示了,访问被拒绝,说明了沙箱启动,同时也验证了默认沙箱——禁止本地文件访问。
再来,我们构建一个Test.policy文件如下:
grant {
permission java.io.FilePermission "D:\\*", "read";
};
这里构建了一条安全策略——允许读取security目录下的文件。
修改启动命令,添加
-Djava.security.policy=D:\\Test.policy
再执行,结果输出了test。
如上例。我们通过自定义policy文件修改了默认沙箱的安全策略,再通过启动参数开启沙箱模式。这样就可以构造我们自己想要的沙箱效果了。
有了上面的基础,我们再来看AccessController就十分清楚了,其中doPrivileged方法,能使一段授信代码获得更大权限。同时AccessController中提供了checkPermission方法检查访问权限。
在分析沙箱时,我们了解到Java类通过类加载器加载的,同时通过其来设置加载类的权限,那java的类加载器是如何工作的呢?下一节我们一起分析类加载器相关的内容。
参考:
https://developer.ibm.com/zh/articles/j-lo-javasecurity/
https://www.zybuluo.com/changedi/note/237999
https://www.cnblogs.com/MyStringIsNotNull/p/8268351.html