本博文转载自java中的安全模型(沙箱机制)html
本博文整合自:Java安全——理解Java沙箱、Java 安全模型介绍、Java的沙箱机制原理入门java
咱们都知道,程序员编写一个Java程序,默认的状况下能够访问该机器的任意资源,好比读取,删除一些文件或者网络操做等。当你把程序部署到正式的服务器上,系统管理员要为服务器的安全承担责任,那么他可能不敢肯定你的程序会不会访问不应访问的资源,为了消除潜在的安全隐患,他可能有两种办法:git
Java安全模型的核心就是Java沙箱(sandbox),什么是沙箱?沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,而且严格限制代码对本地系统资源访问,经过这样的措施来保证对代码的有效隔离,防止对本地系统形成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络。不一样级别的沙箱对这些资源访问的限制也能够不同。程序员
全部的Java程序运行均可以指定沙箱,能够定制安全策略。github
在Java中将执行程序分红本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看做是不受信的。对于授信的本地代码,能够访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱 (Sandbox) 机制。以下图所示安全
但如此严格的安全机制也给程序的功能扩展带来障碍,好比当用户但愿远程代码访问本地系统的文件时候,就没法实现。所以在后续的 Java1.1 版本中,针对安全机制作了改进,增长了安全策略,容许用户指定代码对本地资源的访问权限。以下图所示服务器
在 Java1.2 版本中,再次改进了安全机制,增长了代码签名。不论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不一样的运行空间,来实现差别化的代码执行权限控制。以下图所示markdown
当前最新的安全机制实现,则引入了域 (Domain) 的概念。虚拟机会把全部代码加载到不一样的系统域和应用域,系统域部分专门负责与关键资源进行交互,而各个应用域部分则经过系统域的部分代理来对各类须要的资源进行访问。虚拟机中不一样的受保护域 (Protected Domain),对应不同的权限 (Permission)。存在于不一样域中的类文件就具备了当前域的所有权限,以下图所示网络
以上提到的都是基本的 Java 安全模型概念,在应用开发中还有一些关于安全的复杂用法,其中最经常使用到的 API 就是 doPrivileged。doPrivileged 方法可以使一段受信任代码得到更大的权限,甚至比调用它的应用程序还要多,可作到临时访问更多的资源。有时候这是很是必要的,能够应付一些特殊的应用场景。例如,应用程序可能没法直接访问某些系统资源,但这样的应用程序必须获得这些资源才可以完成功能。dom
字节码校验器(bytecode verifier):确保Java类文件遵循Java语言规范。这样能够帮助Java程序实现内存保护。但并非全部的类文件都会通过字节码校验,好比核心类。
虚拟机为不一样的类加载器载入的类提供不一样的命名空间,命名空间由一系列惟一的名称组成,每个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每个类装载器维护的,它们互相之间甚至不可见。
类装载器采用的机制是双亲委派模式。1. 从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载从而没法使用;
存取控制器(access controller):存取控制器能够控制核心API对操做系统的存取权限,而这个控制的策略设定,能够由用户指定。
安全管理器(security manager):是核心API和操做系统之间的主要接口。实现权限控制,比存取控制器优先级高。
安全软件包(security package):java.security下的类和扩展包下的类,容许用户为本身的应用增长新的安全特性,包括:
权限是指容许代码执行的操做。包含三部分:权限类型、权限名和容许的操做。权限类型是实现了权限的Java类名,是必需的。权限名通常就是对哪类资源进行操做的资源定位(好比一个文件名或者通配符、网络主机等),通常基于权限类型来设置,有的好比java.security.AllPermission不须要权限名。容许的操做也和权限类型对应,指定了对目标能够执行的操做行为,好比读、写等。以下面的例子:
permission java.security.AllPermission; //权限类型 permission java.lang.RuntimePermission "stopThread"; //权限类型+权限名 permission java.io.FilePermission "/tmp/foo" "read"; //权限类型+权限名+容许的操做
说明 | 类型 | 权限名 | 操做 | 例子 |
---|---|---|---|---|
文件权限 | java.io.FilePermission | 文件名(平台依赖) | 读、写、删除、执行 | 容许全部问价的读写删除执行:permission java.io.FilePermission "<< ALL FILES>>", "read,write,delete,execute";。容许对用户主目录的读:permission java.io.FilePermission "${user.home}/-", "read"; |
套接字权限 | java.net.SocketPermission | 主机名:端口 | 接收、监听、链接、解析 | 容许实现全部套接字操做:permission java.net.SocketPermission ":1-", "accept,listen,connect,resolve";。容许创建到特定网站的链接:permission java.net.SocketPermission ".abc.com:1-", "connect,resolve"; |
属性权限 | java.util.PropertyPermission | 须要访问的jvm属性名 | 读、写 | 读标准Java属性:permission java.util.PropertyPermission "java.", "read";。在sdo包中建立属性:permission java.util.PropertyPermission "sdo.", "read,write"; |
运行时权限 | java.lang.RuntimePermission | 多种权限名[见附录A] | 无 | 容许代码初始化打印任务:permission java.lang.RuntimePermission "queuePrintJob" |
AWT权限 | java.awt.AWTPermission | 6种权限名[见附录B] | 无 | 容许代码充分使用robot类:permission java.awt.AWTPermission "createRobot"; permission java.awt.AWTPermission "readDisplayPixels"; |
网络权限 | java.net.NetPermission | 3种权限名[见附录C] | 无 | 容许安装流处理器:permission java.net.NetPermission "specifyStreamHandler";。 |
安全权限 | java.security.SecurityPermission | 多种权限名[见附录D] | 无 | |
序列化权限 | java.io.SerializablePermission | 2种权限名[见附录E] | 无 | |
反射权限 | java.lang.reflect.ReflectPermission | suppressAccessChecks(容许利用反射检查任意类的私有变量) | 无 | |
彻底权限 | java.security.AllPermission | 无(拥有执行任何操做的权限) | 无 |
代码源是类所在的位置,表示为以URL地址。
保护域用来组合代码源和权限,这是沙箱的基本概念。保护域就在于声明了好比由代码A能够作权限B这样的事情。
策略文件是控制沙箱的管理要素,一个策略文件包含一个或多个保护域的项。策略文件完成了代码权限的指定任务,策略文件包括全局和用户专属两种。
为了管理沙箱,策略文件我认为是最重要的内容。JVM可使用多个策略文件,不过通常两个最经常使用。一个是全局的:$JREHOME/lib/security/java.policy,做用于JVM的全部实例。另外一个是用户本身的,能够存储到用户的主目录下。策略文件可使用jdk自带的policytool工具编辑。
默认的策略文件咱们先参考一下:
// 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"; };
策略文件的内容格式就是这样,grant受权容许操做某个权限。这个默认的策略文件就指明了jdk扩展包能够有所有权限,容许代码stop线程,容许监听1099端口(1099号端口,是默认的服务器端RMI监听端口)等等。
另外一个很重要的是参数文件——java.security,这个文件和策略文件在同一个目录下。这个参数文件定义了沙箱的一些参数。好比默认的沙箱文件是这样的(截取部分):
# The default is to have a single system-wide policy file, # and a policy file in the user's home directory. policy.url.1=file:${java.home}/lib/security/java.policy policy.url.2=file:${user.home}/.java.policy # whether or not we expand properties in the policy file # if this is set to false, properties (${...}) will not be expanded in policy # files. policy.expandProperties=true # whether or not we allow an extra policy to be passed on the command line # with -Djava.security.policy=somefile. Comment out this line to disable # this feature. policy.allowSystemProperty=true
policy.url.*这个属性指明了使用的策略文件,如上文所述,默认的两个位置就在这里配置,用户能够自行更改顺序和存储位置。而policy.allowSystemProperty指明是否容许用户自行经过命令行指定policy文件。
保存密钥证书的地方。
经过Java命令行启动的Java应用程序,默认不启用沙箱。要想启用沙箱,启动命令须要作以下形式的变动:
java -Djava.security.manager <other args>
沙箱启动后,安全管理器会使用两个默认的策略文件来肯定沙箱启动参数。固然也能够经过命令指定:
java -Djava.security.policy=<URL>
若是要求启动时只遵循一个策略文件,那么启动参数要加个等号,以下:
java -Djava.security.policy==<URL>
这个例子很简单,首先写一个r.txt文件,里面的内容是“abcd”,再写个程序以下读取这个r.txt。
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; public class PolicyTest { public static void file() { File f = new File("D:\\github\\CDLib\\src\\main\\resources\\security\\r.txt"); InputStream is; try { is = new FileInputStream(f); byte[] content = new byte[1024]; while (is.read(content) != -1) { System.out.println(new String(content)); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static void main(String[] args) { // test read file. file(); } }
发现输出是abcd。
接下来修改java启动参数,加入-Djava.security.manager,启动了安全沙箱。再运行,输出变成了异常
Exception in thread "main" java.security.AccessControlException: access denied ("java.io.FilePermission" "D:\github\CDLib\src\main\resources\security\r.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.taobao.cd.security.PolicyTest.main(PolicyTest.java:15)
这里已经提示了,访问被拒绝,说明了沙箱启动,同时也验证了默认沙箱——禁止本地文件访问。
再来,咱们构建一个custom.policy文件以下:
grant { permission java.io.FilePermission "D:\\github\\CDLib\\src\\main\\resources\\security\\*", "read"; };
这里构建了一条安全策略——容许读取security目录下的文件。
修改启动命令,添加
-Djava.security.policy=D:\\github\\CDLib\\src\\main\\resources\\security\\custom.policy
再执行,结果输出了abcd。
如上例。咱们经过自定义policy文件修改了默认沙箱的安全策略,再经过启动参数开启沙箱模式。这样就能够构造咱们本身想要的沙箱效果了。
权限名 | 用途说明 |
---|---|
accessClassInPackage. | 容许代码访问指定包中的类 |
accessDeclaredMembers | 容许代码使用反射访问其余类中私有或保护的成员 |
createClassLoader | 容许代码实例化类加载器 |
createSecurityManager | 容许代码实例化安全管理器,它将容许程序化的实现对沙箱的控制 |
defineClassInPackage. | 容许代码在指定包中定义类 |
exitVM | 容许代码关闭整个虚拟机 |
getClassLoader | 容许代码访问类加载器以得到某个特定的类 |
getProtectionDomain | 容许代码访问保护域对象以得到某个特定类 |
loadlibrary. | 容许代码装载指定类库 |
modifyThread | 容许代码调整指定的线程参数 |
modifyThreadGroup | 容许代码调整指定的线程组参数 |
queuePrintJob | 容许代码初始化一个打印任务 |
readFileDescriptor | 容许代码读文件描述符(相应的文件是由其余保护域中的代码打开的) |
setContextClassLoader | 容许代码为某线程设置上下文类加载器 |
setFactory | 容许代码建立套接字工厂 |
setIO | 容许代码重定向System.in、System.out或System.err输入输出流 |
setSecurityManager | 容许代码设置安全管理器 |
stopThread | 容许代码调用线程类的stop()方法 |
writeFileDescriptor | 容许代码写文件描述符 |
权限名 | 用途说明 |
---|---|
accessClipboard | 容许访问系统的全局剪贴板 |
accessEventQueue | 容许直接访问事件队列 |
createRobot | 容许代码建立AWT的Robot类 |
listenToAllAWTEvents | 容许代码直接监听事件分发 |
readDisplayPixels | 容许AWT Robot读显示屏上的像素 |
showWindowWithoutWarningBanner | 容许建立无标题栏的窗口 |
权限名 | 用途说明 |
---|---|
specifyStreamHandler | 容许在URL类中安装新的流处理器 |
setDefaultAuthenticator | 能够安装鉴别类 |
requestPassworkAuthentication | 能够完成鉴别 |
权限名 | 用途说明 |
---|---|
addIdentityCertificate | 为Identity增长一个证书 |
clearProviderProperties. | 针对指定的提供者,删除全部属性 |
createAccessControlContext | 容许建立一个存取控制器的上下文环境 |
getDomainCombiner | 容许撤销保护域 |
getPolicy | 检索能够实现沙箱策略的类 |
getProperty. | 读取指定的安全属性 |
getSignerPrivateKey | 由Signer对象获取私有密钥 |
insertProvider. | 将指定的提供者添加到响应的安全提供者组中 |
loadProviderProperties. | 装载指定的提供者的属性 |
printIdentity | 打印Identity类内容 |
putAllProviderProperties. | 更新指定的提供者的属性 |
putProviderProperty. | 为指定的提供者增长一个属性 |
removeIdentityCertificate | 取消Identity对象的证书 |
removeProvider. | 将指定的提供者从相应的安全提供者组中删除 |
removeProviderProperty. | 删除指定的安全提供者的某个属性 |
setIdentityInfo | 为某个Identity对象设置信息串 |
setIdentityPublicKey | 为某个Identity对象设置公钥 |
setPolicy | 设置能够实现沙箱策略的类 |
setProperty. | 设置指定的安全属性 |
setSignerKeyPair | 在Signer对象中设置密钥对 |
setSystemScope | 设置系统所用的IdentityScope |
权限名 | 用途说明 |
---|---|
enableSubstitution | 容许实现ObjectInputStream类的enableResolveObject()方法和ObjectOutputStream类的enableReplaceObject()方法 |
enableSubclassImplementation | 容许ObjectInputStream和ObjectOutputStream建立子类,子类能够覆盖readObject()和writeObject()方法 |