java冷知识:应用程序安全沙箱

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/itsoftchenfei/article/details/87921828

在阅读jdk源码时经常遇到System.getSecurityManager();这样安全检查代码。它保证了jvm所运行程序的完整性,使得jvm不会因为运行有漏洞或恶意的代码而导致出现不可预期的状态。

public class File implements Serializable, Comparable<File>{    
    public boolean canRead() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(path);//检查
        }
        if (isInvalid()) {
            return false;
        }
        return fs.checkAccess(this, FileSystem.ACCESS_READ);
    }
}
public class Socket implements java.io.Closeable {
    public void connect(SocketAddress endpoint, int timeout) throws IOException {   
        InetSocketAddress epoint = (InetSocketAddress) endpoint;
        InetAddress addr = epoint.getAddress ();
        int port = epoint.getPort();
        checkAddress(addr, "connect");

        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            if (epoint.isUnresolved())
                security.checkConnect(epoint.getHostName(), port);//检查
            else
                security.checkConnect(addr.getHostAddress(), port);
        }
       ...
    }
}

1. 为什么 Java 语言需要这样的安全检查代码呢?

Java 编写的不只是服务端应用程序,它还可以作为客户端跑在浏览器上(Applet),它还可以以 app 的形式跑在手机上(J2ME),针对不同的平台 JVM 会使用不同的安全策略。对于 Applet 而言,受限尤其严苛,通常都不允许 Applet 来操作本地文件。

2. java 的安全检查管理器和操作系统的权限检查是同一个概念吗?

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

似乎所有和 IO 操作有关的方法调用都需要进行安全检查。跟 IO 操作相关的权限检查似乎还可以理解,不是所有的 IO 资源用户进程都是可以随意访问的。但是连环境变量都不让随意读,而且限制的还不是所有环境变量,而是某个具体的环境变量,这安全检查是不是有点过了?这是因为 Java 的安全检查管理器和操作系统的权限检查不是一个概念,待 Java 的安全检查通过后执行具体的 IO 操作时,操作系统还会继续进行权限检查

3. 沙箱的安全检查点有哪些?

  1. 文件操作
  2. 套接字操作
  3. 线程和线程组
  4. 类加载器控制
  5. 反射控制
  6. 线程堆栈信息获取
  7. 网络代理控制
  8. Cookie 读写控制

4. 如何判断是否通过安全检查?

安全检查没有通过,那就会抛出 java.security.AccessControlException 异常。即使安全检查通过了,操作系统的权限检查仍然可能通不过,这时候又会抛出其它类型的异常。

5. 有必要开启安全检查?

我们平时在本地运行 java 程序时通常都不会默认打开安全检查器。启用安全检查,将会降低程序的执行效率,如果policy 配置文件配置多了,那么检查效率就会很慢。不过用 Java 来编写服务端程序似乎开启安全检查没有任何必要。

6. 如何开启安全检查呢?

添加jvm 参数:

java -Djava.security.manager xxx
java -Djava.security.manager -DDjava.security.policy="${policypath}"

指定安全策略文件:
{policypath}需要提供具体路径,默认的策略文件路径是 JAVA_HOME/jre/lib/security/java.policy,它的内容如下

// 内置扩展库授权规则
// 表示 JAVA_HOME/jre/lib/ext/ 目录下的类库可以全权访问任意资源
// 包含 javax.swing.*, javax.xml.*, javax.crypto.* 等等
grant codeBase "file:${{java.ext.dirs}}/*" {
 permission java.security.AllPermission;
};

// 其它类库授权规则
grant {
 // 允许线程调用自己的 stop 方法自杀
 permission java.lang.RuntimePermission "stopThread";
 // 允许程序监听 localhost 的随机可用端口,不允许随意订制端口
 permission java.net.SocketPermission "localhost:0", "listen";
 // 限制获取系统属性,下面一系列的配置都是只允许读部分内置属性
 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 如果提供了 codeBase 参数就是针对具体的类库来配置权限规则,如果没有指定 codeBase 就是针对所有其它类库配置的规则。

上面的配置意味着启用默认安全策略的 JVM 将无法访问本地文件。如果需要访问本地文件,可以增加下面的规则

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

上面Permission 的配置参数正好对应了它的构造器参数
public FilePermission(String path, String actions) {
  super(path);
  init(getMask(actions));
}

7. 如何自定义权限规则?

Java 默认安全规则分为几大模块,每个模块都有各自的配置参数。其中 AllPermission 表示打开所有权限。还有一个不速之客 HibernatePermission,它并不是内置的权限模块,它是 Hibernate 框架为自己订制的,这意味着安全规则是支持自定义扩展的。扩展也很简单,可以自己编写一个 Permission 子类,实现它的 4 个抽象方法。

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

class CustomPermission extends Permission {
  private String actions;
  CustomPermission(string name, string actions) {
    super(name)
    this.actions = actions;
  }
  ...
}
//JVM 启动时会将 profile 里面定义的权限规则加载到权限池中
class CustomAPI {
  public void someMethod() {
    SecurityManager sec = System.getSecurityManager();
    if(sec != null) {
      sec.CheckPermission(new CustomPermission("xname", "xactions"));
    }
    ...
  }
}

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

猜你喜欢

转载自blog.csdn.net/itsoftchenfei/article/details/87921828