Java SecurityManager初识

说明

最近整理文档的时候,发现在角落里躺着几篇有关Java SecurityManager的文章,最近也没有精力研究Java安全了,就将文章分享出来。

简介

根据Oracle官方对The Security Manager的说明:

A security manager is an object that defines a security policy for an application. This policy specifies actions that are unsafe or sensitive. Any actions not allowed by the security policy cause a SecurityException to be thrown. An application can also query its security manager to discover which actions are allowed. Typically, a web applet runs with a security manager provided by the browser or Java Web Start plugin. Other kinds of applications normally run without a security manager, unless the application itself defines one. If no security manager is present, the application has no security policy and acts without restrictions.

翻译为中文的意思是:Security Manager是用于对一个应用程序定义一个安全策略的对象。这个策略能够定义一些不安全或者是敏感操作。不被安全策略允许的操作将会抛出SecurityException。同样地,利用Security Manager还可以定义一些允许执行的操作。默认情况下,oracle已经对Security Manager能够控制的操作进行了说明Permissions in the Java Development Kit (JDK),包括Socket、文件、序列化、反射等权限。

Security Manager主要是在运行时检查。默认情况下,直接运行的Java程序都没有开启SecurityManager。如果程序开启了SecurityManager,那么程序所进行的任何操作最终都会进入到Security Manager进行检查判断。Java程序有两种方式开启Security Manager以及定义操作。这两种方式都会在本文中进行说明。

  1. 自定义SecurityManager策略文件
  2. 继承SecurityManager,通过代码的方式,对每个操作进行控制。

自定义SecurityManager策略文件

虽然Java程序默认没有开启Security Manager,但是我们可以通过在JVM启动参数中加上-Djava.security.manager开启。具体用法是java -Djava.security.manager classfile。开启之后,JVM首先会去${java.home}/jre/lib/security寻找java.security文件。在java.security定义了很多与安全相关的配置。例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# List of comma-separated packages that start with or equal this string
# will cause a security exception to be thrown when
# passed to checkPackageAccess unless the
# corresponding RuntimePermission ("accessClassInPackage."+package) has
# been granted.
package.access=sun.,\
com.sun.xml.internal.,\
com.sun.imageio.,\
com.sun.istack.internal.,\
com.sun.jmx.,\
com.sun.media.sound.,\
com.sun.naming.internal.,\
com.sun.proxy.,\
com.sun.corba.se.,\
com.sun.org.apache.bcel.internal.,\

....
# List of comma-separated packages that start with or equal this string
# will cause a security exception to be thrown when
# passed to checkPackageDefinition unless the
# corresponding RuntimePermission ("defineClassInPackage."+package) has
# been granted.
#
# by default, none of the class loaders supplied with the JDK call
# checkPackageDefinition.
#
package.definition=sun.,\
com.sun.xml.internal.,\
com.sun.imageio.,\
com.sun.istack.internal.,\
com.sun.jmx.,\
com.sun.media.sound.,\
com.sun.naming.internal.,\
com.sun.proxy.,\

如果直接访问package.accesspackage.definition中的类会抛出security exception,除非这些定义的类是被允许的。

还有比如:

1
2
3
4
# Determines whether this properties file can be appended to
# or overridden on the command line via -Djava.security.properties
#
security.overridePropertiesFile=true

设置了security.overridePropertiesFile=true表示可以覆盖或者是扩展默认的策略的配置文件。(这一点在后面会进行说明)

还有类似于以-Djava.security.policy=somefile的方式载入自定义的策略文件:

1
2
3
4
# 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

其中还包括了策略文件的定义:

1
2
3
4
# 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

默认的策略文件的定义是在${java.home}/lib/security/java.policy。内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

// Standard extensions get all permissions by default

grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};

// default permissions granted to all domains

grant {
permission java.lang.RuntimePermission "stopThread";

// allows anyone to listen on dynamic ports
permission java.net.SocketPermission "localhost:0", "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";
};

分析java.policy文件

  1. "file:$/*"使用了permission java.security.AllPermission,表示开启了所有的权限。
  2. permission java.lang.RuntimePermission "stopThread";表示运行调用进程的暂停方法。
  3. 系统属性的读取,如permission java.util.PropertyPermission "java.version", "read";表示允许读取java.version

我们以permission java.util.PropertyPermission "java.version", "read";为例进行说明。编写测试文件

Test.java

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
System.out.println(System.getProperty("java.version"));
System.setProperty("java.version","123");
System.out.println(System.getProperty("java.version"));
}
}

在不开启Security Manager的情况下运行java Test得到的结果是:

1
2
3
$ java Test
1.8.0_161
123

可以看到通过System.setProperty("java.version","123");,我们改变了java.version的值。

开启Security Manager的情况下运行java Test得到的结果是:

1
2
3
4
5
6
7
8
$ java -Djava.security.manager Test
1.8.0_161
Exception in thread "main" java.security.AccessControlException: access denied ("java.util.PropertyPermission" "java.version" "write")
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
at java.security.AccessController.checkPermission(AccessController.java:884)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
at java.lang.System.setProperty(System.java:792)
at Test.main(Test.java:4)

由于在默认的java.policy中仅仅只是设置允许读取java.version,所以尝试进行设置java.version就会抛出AccessControlException异常。

我们可以自定义我们的策略文件。例如我自定义的策略文件如下:
my.policy

1
2
3
grant {
permission java.util.PropertyPermission "java.version", "read";
};

表示只会允许读取java.version信息。

Test.java

1
2
3
4
5
6
7
8
9
import java.io.IOException;

public class Test {
public static void main(String[] args) throws IOException {
System.out.println(System.getProperty("java.version"));
System.setProperty("java.version","123");
System.out.println(System.getProperty("java.version"));
}
}

我们的策略文件的写法是:
my.policy

1
2
3
grant {
permission java.util.PropertyPermission "java.version", "write";
};

使用添加模式,命令是java -Djava.security.manager -Djava.security.policy=D:/my.policy(注意其中的-Djava.security.policy=D:/my.policy只有一个等号)。得到的结果如下:

1
2
3
$ java -Djava.security.manager -Djava.security.policy=D:/my.policy Test
1.8.0_161
123

如果我们变为覆盖模式,命令是java -Djava.security.manager -Djava.security.policy==D:/my.policy(注意其中的-Djava.security.policy==D:/my.policy只有两个等号)。得到的结果如下:

1
2
3
4
5
6
7
8
$ java -Djava.security.manager -Djava.security.policy==D:/my.policy Test
Exception in thread "main" java.security.AccessControlException: access denied ("java.util.PropertyPermission" "java.version" "read")
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
at java.security.AccessController.checkPermission(AccessController.java:884)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
at java.lang.SecurityManager.checkPropertyAccess(SecurityManager.java:1294)
at java.lang.System.getProperty(System.java:717)
at Test.main(Test.java:8)

可以看到直接报错,因为此时只为java.version设置了write权限没有读权限,所以System.getProperty("java.version")就会报错。

以上就是利用策略文件实现对于权限的访问控制。

参考

The Security Manager
Java安全管理器-SecurityManager
Permissions in the Java Development Kit (JDK)