Java应用程序的安全沙箱机制是什么

如果你经常阅读源码,你会发现 Java 的源码中到处都有类似于下面这一段代码

class File {<br/>  // 判断一个磁盘文件是否存在<br/>  public boolean exists() {<br/>    SecurityManager security = System.getSecurityManager();<br/>    if (security != null) {<br/>      security.checkRead(path);<br/>    }<br/>    ...<br/>  }<br/>}<br/>

这明显是一个安全检查代码,检查的是你是否有访问磁盘路径的权限,为什么 Java 语言需要这样的安全检查代码呢?我们再看看客户端套接字的 connect 函数源码,它需要检查用户是否有connect 某个网络地址的权限

class Socket {<br/>  public void connect(SocketAddress endpoint, int timeout) {<br/>    ...<br/>    SecurityManager security = System.getSecurityManager();<br/>    if (security != null) {<br/>       if (epoint.isUnresolved())<br/>          security.checkConnect(epoint.getHostName(), port);<br/>       else<br/>          security.checkConnect(addr.getHostAddress(), port);<br/>       }<br/>    }<br/>    ...<br/>  }<br/>}<br/>

再看服务端套接字的源码,它会检查端口的监听权限

class ServerSocket {<br/>  public void bind(SocketAddress endpoint, int backlog) {<br/>    ...<br/>    SecurityManager security = System.getSecurityManager();<br/>    if (security != null)<br/>       security.checkListen(epoint.getPort());<br/>    ...<br/>  }<br/>}<br/>

似乎所有和 IO 操作有关的方法调用都需要进行安全检查。看起来IO操作相关的权限检查是可理解的,用户进程不能随意访问所有的IO资源。但是连环境变量都不让随意读,而且限制的还不是所有环境变量,而是某个具体的环境变量,这安全检查是不是有点过了?

class System {<br/>  public static String getenv(String name) {<br/>    SecurityManager sm = getSecurityManager();<br/>    if (sm != null) {<br/>       sm.checkPermission(new RuntimePermission("getenv."+name));<br/>    }<br/>    return ProcessEnvironment.getenv(name);<br/>  }<br/>}<br/>

这是因为 Java 的安全检查管理器和操作系统的权限检查不是一个概念,Java 编写的不只是服务端应用程序,它还可以作为客户端跑在浏览器上(Applet),它还可以以 app 的形式跑在手机上(J2ME),针对不同的平台 jvm 会使用不同的安全策略。通常情况下,针对 Applet 的限制非常严格,一般不允许 Applet 操作本地文件。执行具体 IO 操作前,一旦 Java 的安全检查通过,操作系统仍会进行权限检查。

立即学习Java免费学习笔记(深入)”;

我们平时在本地运行 java 程序时通常都不会默认打开安全检查器,需要执行 jvm 参数才会打开

$ java -Djava.security.manager xxx<br/>$ java -Djava.security.manager -DDjava.security.policy="${policypath}"<br/>

因为安全限制条件可以定制,所以还需要提供具体的安全策略文件路径,默认的策略文件路径是 JAVA_HOME/jre/lib/security/java.policy,下面让我们来看看这个文件里都写了些什么

// 内置扩展库授权规则<br/>// 表示 JAVA_HOME/jre/lib/ext/ 目录下的类库可以全权访问任意资源<br/>// 包含 javax.swing.*, javax.xml.*, javax.crypto.* 等等<br/>grant codeBase "file:${{java.ext.dirs}}/*" {<br/> permission java.security.AllPermission;<br/>};<br/><br/>// 其它类库授权规则<br/>grant {<br/> // 允许线程调用自己的 stop 方法自杀<br/> permission java.lang.RuntimePermission "stopThread";<br/> // 允许程序监听 localhost 的随机可用端口,不允许随意订制端口<br/> permission java.net.SocketPermission "localhost:0", "listen";<br/> // 限制获取系统属性,下面一系列的配置都是只允许读部分内置属性<br/> permission java.util.PropertyPermission "java.version", "read";<br/> permission java.util.PropertyPermission "java.vendor", "read";<br/> permission java.util.PropertyPermission "java.vendor.url", "read";<br/> permission java.util.PropertyPermission "java.class.version", "read";<br/> permission java.util.PropertyPermission "os.name", "read";<br/> permission java.util.PropertyPermission "os.version", "read";<br/> permission java.util.PropertyPermission "os.arch", "read";<br/> permission java.util.PropertyPermission "file.separator", "read";<br/> permission java.util.PropertyPermission "path.separator", "read";<br/> permission java.util.PropertyPermission "line.separator", "read";<br/> permission java.util.PropertyPermission "java.specification.version", "read";<br/> permission java.util.PropertyPermission "java.specification.vendor", "read";<br/> permission java.util.PropertyPermission "java.specification.name", "read";<br/> permission java.util.PropertyPermission "java.vm.specification.version", "read";<br/> permission java.util.PropertyPermission "java.vm.specification.vendor", "read";<br/> permission java.util.PropertyPermission "java.vm.specification.name", "read";<br/> permission java.util.PropertyPermission "java.vm.version", "read";<br/> permission java.util.PropertyPermission "java.vm.vendor", "read";<br/> permission java.util.PropertyPermission "java.vm.name", "read";<br/>};<br/>

grant 如果提供了 codeBase 参数就是针对具体的类库来配置权限规则,如果没有指定 codeBase 就是针对所有其它类库配置的规则。

安全检查没有通过,那就会抛出 java.security.AccessControlException 异常。即使通过了安全检查,操作系统的权限检查也有可能失败,此时将会抛出其他类型的异常。

如果按照上面所配置的规则,使用默认安全策略的 JVM 将无法访问本地文件,因为授权规则使用的是白名单。如果需要访问本地文件,可以增加下面的规则

permission java.io.FilePermission "/etc/passwd", "read";<br/>permission java.io.FilePermission "/etc/shadow", "read,write";<br/>permission java.io.FilePermission "/xyz", "read,write,delete";<br/>// 允许读所有文件<br/>permission java.io.FilePermission "*", "read";<br/>

Permission 的配置参数正好对应了它的构造器参数

public FilePermission(String path, String actions) {<br/>  super(path);<br/>  init(getMask(actions));<br/>}<br/>

Java 默认安全规则分为几大模块,每个模块都有各自的配置参数

Java应用程序的安全沙箱机制是什么

其中 AllPermission 表示打开所有权限。还有一个不速之客 hibernatePermission,它并不是内置的权限模块,它是 Hibernate 框架为自己订制的,这意味着安全规则是支持自定义扩展的。要扩展很容易,只需编写一个 Permission 子类,并实现其四个抽象方法。

abstract class Permission {<br/>  // 权限名称,对于文件来说就是文件名,对于套接字来说就是套接字地址<br/>  // 它的意义是子类可定制的<br/>  private String name;<br/>  // 当前权限对象是否隐含了 other 权限<br/>  // 比如 AllPermission 的这个方法总是返回 true<br/>  public abstract boolean implies(Permission other);<br/>  // equals 和 hashcode 用于权限比较<br/>  public abstract boolean equals(Object obj);<br/>  public abstract int hashCode();<br/>  // 权限选项 read,write,xxx<br/>  public abstract String getActions();<br/>}<br/><br/>class CustomPermission extends Permission {<br/>  private String actions;<br/>  CustomPermission(string name, string actions) {<br/>    super(name)<br/>    this.actions = actions;<br/>  }<br/>  ...<br/>}<br/>

JVM 启动时会将 profile 里面定义的权限规则加载到权限池中,用户程序在特定的 API 方法里使用权限池来判断是否包含调用这个 API 的权限,最终会落实到调用权限池中每一个权限对象的 implies 方法来判断是否具备指定权限。

class CustomAPI {<br/>  public void someMethod() {<br/>    SecurityManager sec = System.getSecurityManager();<br/>    if(sec != null) {<br/>      sec.CheckPermission(new CustomPermission("xname", "xactions"));<br/>    }<br/>    ...<br/>  }<br/>}<br/>

启用安全检查,将会降低程序的执行效率,如果 profile 里面定义的权限规则特别多,那么检查效率就会很慢,使用时注意安全检查要省着点使用。

沙箱的安全检查点非常多,下面列举一些常见的场景

  1. 文件操作

  2. 套接字操作

  3. 线程和线程组

  4. 类加载器控制

  5. 反射控制

  6. 线程信息获取

  7. 网络代理控制

  8. Cookie 读写控制

如果你的服务端程序开启了安全检查,就需要在 policy 配置文件里打开很多安全设置,非常繁琐,而且配置多了,检查的性能也会产生一定损耗。这点有点类似 android 的应用权限设置,在每个 Android 应用的配置文件里都需要罗列出一系列应用子权限。不过用 Java 来编写服务端程序似乎开启安全检查没有任何必要。

© 版权声明
THE END
喜欢就支持一下吧
点赞10 分享