在阅读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. 沙箱的安全检查点有哪些?
- 文件操作
- 套接字操作
- 线程和线程组
- 类加载器控制
- 反射控制
- 线程堆栈信息获取
- 网络代理控制
- 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 里面定义的权限规则特别多,那么检查效率就会很慢,使用时注意安全检查要省着点使用。