自定义java.policy配置(如何让你的类禁止读写文件?禁止创建Socket对象?)

时间:2021-09-03 00:20:28
最近做的项目有一个特殊的需求,由于系统是CS的,客户端需要自己编写JAVA类上传至服务器运行,整个流程如下:

1.客户端编写JAVA类,并实现特定接口

2.由客户端的JAVA编译器将其编译成class

3.客户端通过socket将class文件上传至服务器

4.服务器将class文件放至自己的classpath中,加载并执行

问题出在最后一步上,服务器虽然由我们完全控制,客户端编写的类虽然需要实现特定的接口,但执行具体内容不在我们控制范围内。也就是说,客户端上传的类中,完全可能写一段程序,读取服务器的所有文件,然后上传给另一台客户端可控制的机器上,这样就很容易地窃取了我们的整个系统。为了避免这种情况,能想到的解决方案有:

1.服务器接收到客户端上传的class后,不立即执行,而是增加一个审核的步骤,当上传的class通过审核后,才能够执行

2.让客户端上传的class在独立的沙箱中运行,类似于applet

第一种方案理论上是可行的,但是审核的步骤靠程序来实现相当困难,你需要先反编译class,然后再检查里面是否有类似file.listFiles(),reader.read(byte[] arr)这样的调用,这几乎无法实现。因为JAVA读取文件的方式有很多种,并且创建对象的方式也有很多种,可以直接new,也可以使用反射去创建,甚至调用读取方法也不直接调用,而使用反射去调,你如何使用程序去检查呢?那么能不能把审核的这一步骤交给人来做呢,安排一工作人员在class上传后先看看里面有没有恶意代码。这当然是可以的,但不是多出一个人员成本了吗?

第二种方案是比较理想的,即把你想控制的访问权限通过配置的方式告诉JVM,剩下的工作交给JVM来做。

虽然有了方向,但由于以前没有做过类似的功能,所以决定先上网搜一把。搜索的关键字为“java.policy配置”或“java沙箱模型”。不过很遗憾,文章虽然有很多,但经过自己动手测试才发现,写文章的人几乎没有人真正实践过,要么互相抄袭,要么就是随便翻译一下国外的文章。没办法,还是看JDK的文档吧,原文地址如下:

http://docs.oracle.com/javase/1.5.0/docs/guide/security/PolicyFiles.html

虽然写的不是很详细,但还算到位,关键的地方都写到了。经过一番摸索,终于实现了我想要的功能,客户端上传的代码如果有读写文件或者创建socket对象的操作,程序就会报权限不足的错误。这个功能的实现不需要修改服务器的代码,实在是太棒了。现将主要步骤分享如下:

1.启动SecurityManager开关

默认情况下,JVM是不启动安全检查的,所以要想让程序在沙箱中运行,必须将开关打开。打开的方式有两种,一种是在启动运行中追加JVM参数-Djava.security.manager,还有一种方式是在程序中直接设置:System.setSecurityManager(new SecurityManager());,这两种方式是等价的

2.修改jvm自带的java.policy文件,

java.policy文件位于%JAVA_HOME%/ jre/lib/security/下,默认内容如下:

[plain] view plain copy
  1. // Standard extensions get all permissions by default  
  2.   
  3. grant codeBase "file:${{java.ext.dirs}}/*" {  
  4.     permission java.security.AllPermission;  
  5. };  
  6.   
  7. // default permissions granted to all domains  
  8.   
  9. grant {   
  10.     // Allows any thread to stop itself using the java.lang.Thread.stop()  
  11.     // method that takes no argument.  
  12.     // Note that this permission is granted by default only to remain  
  13.     // backwards compatible.  
  14.     // It is strongly recommended that you either remove this permission  
  15.     // from this policy file or further restrict it to code sources  
  16.     // that you specify, because Thread.stop() is potentially unsafe.  
  17.     // See "http://java.sun.com/notes" for more information.  
  18.     permission java.lang.RuntimePermission "stopThread";  
  19.   
  20.     // allows anyone to listen on un-privileged ports  
  21.     permission java.net.SocketPermission "localhost:1024-", "listen";  
  22.   
  23.     // "standard" properies that can be read by anyone  
  24.   
  25.     permission java.util.PropertyPermission "java.version", "read";  
  26.     permission java.util.PropertyPermission "java.vendor", "read";  
  27.     permission java.util.PropertyPermission "java.vendor.url", "read";  
  28.     permission java.util.PropertyPermission "java.class.version", "read";  
  29.     permission java.util.PropertyPermission "os.name", "read";  
  30.     permission java.util.PropertyPermission "os.version", "read";  
  31.     permission java.util.PropertyPermission "os.arch", "read";  
  32.     permission java.util.PropertyPermission "file.separator", "read";  
  33.     permission java.util.PropertyPermission "path.separator", "read";  
  34.     permission java.util.PropertyPermission "line.separator", "read";  
  35.   
  36.     permission java.util.PropertyPermission "java.specification.version", "read";  
  37.     permission java.util.PropertyPermission "java.specification.vendor", "read";  
  38.     permission java.util.PropertyPermission "java.specification.name", "read";  
  39.   
  40.     permission java.util.PropertyPermission "java.vm.specification.version", "read";  
  41.     permission java.util.PropertyPermission "java.vm.specification.vendor", "read";  
  42.     permission java.util.PropertyPermission "java.vm.specification.name", "read";  
  43.     permission java.util.PropertyPermission "java.vm.version", "read";  
  44.     permission java.util.PropertyPermission "java.vm.vendor", "read";  
  45.     permission java.util.PropertyPermission "java.vm.name", "read";  
  46. };  

 

里面定义了JAVA程序默认的权限,第一个grant定义了系统属性${{java.ext.dirs}}路径下的所有的class及jar(/*号表示所有class和jar,如果只是/则表示所有class但不包括jar)拥有所有的操作权限(java.security.AllPermission),java.ext.dirs对应路径为%JAVA_HOME%/jre/lib/ext目录,而第二个grant后面定义了所有JAVA程序都拥有的权限,包括停止线程、启动Socket 服务器、读取部分系统属性。由于我要禁止客户端的class类启动socket,所以需删除或注释掉下面这句:

permission java.net.SocketPermission "localhost:1024-", "listen";

3.指定自己的policy文件,为了便于理解,这里我举个例子。假设客户端的class我放在C:/tmp目录下,假设类的代码如下:

[java] view plain copy
  1. package com.test;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.File;  
  5. import java.io.FileInputStream;  
  6. import java.io.FileWriter;  
  7. import java.io.InputStream;  
  8. import java.io.InputStreamReader;  
  9. import java.lang.reflect.Method;  
  10. import java.net.ServerSocket;  
  11. import java.net.Socket;  
  12.   
  13. public class PolicyTest {  
  14.       
  15.     public static void main(String[] args) {  
  16.         try {  
  17.             Class clazz = Class.forName("java.lang.Object");  
  18.             Object o = clazz.newInstance();  
  19.             Method m = o.getClass().getDeclaredMethod("toString");  
  20.             m.invoke(o);  
  21.             System.out.println("reflect access ok");  
  22.         } catch (Throwable e) {  
  23.             System.out.println(e.getMessage());  
  24.         }  
  25.           
  26.         try {  
  27.             String userdir = System.getProperty("user.home");  
  28.             System.out.println("property read ok");  
  29.             // File f = new File(userdir + "/conf/x.properties");  
  30.         } catch (Throwable e) {  
  31.             System.out.println(e.getMessage());  
  32.         }  
  33.           
  34.         try {  
  35.             ServerSocket s = new ServerSocket(8889);  
  36.             System.out.println("create socket server ok");  
  37.         } catch (Throwable e) {  
  38.             System.out.println(e.getMessage());  
  39.         }  
  40.   
  41.         try {  
  42.             Socket s2 = new Socket("localhost"8889);  
  43.             s2.close();  
  44.             System.out.println("create socket client ok");  
  45.         } catch (Throwable e) {  
  46.             System.out.println(e.getMessage());  
  47.         }  
  48.   
  49.         File file = new File("C:/test.txt");  
  50.         try {  
  51.             read(file);  
  52.             System.out.println("file read ok");  
  53.         } catch (Throwable e) {  
  54.             System.out.println(e.getMessage());  
  55.         }  
  56.   
  57.         try {  
  58.             write(file);  
  59.             System.out.println("file write ok");  
  60.         } catch (Throwable e) {  
  61.             System.out.println(e.getMessage());  
  62.         }  
  63.     }  
  64.   
  65.     private static void read(File file) throws Throwable {  
  66.         InputStream in = null;  
  67.         BufferedReader reader = null;  
  68.         try {  
  69.             in = new FileInputStream(file);  
  70.             reader = new BufferedReader(new InputStreamReader(in));  
  71.             String temp = null;  
  72.             while ((temp = reader.readLine()) != null) {  
  73.                 // System.out.println("read-->" + temp);  
  74.             }  
  75.         } catch (Throwable e) {  
  76.             throw e;  
  77.         } finally {  
  78.             if (in != null) {  
  79.                 in.close();  
  80.             }  
  81.             if (reader != null) {  
  82.                 reader.close();  
  83.             }  
  84.         }  
  85.     }  
  86.   
  87.     private static void write(File file) throws Throwable {  
  88.         FileWriter fw = new FileWriter(file);  
  89.         for (int i = 0; i < 10; i++) {  
  90.             String temp = new java.util.Date() + " " + new java.util.Random().nextLong();  
  91.             // System.out.println("write-->" + temp);  
  92.             fw.write(temp + "\r\n");  
  93.         }  
  94.         fw.flush();  
  95.         fw.close();  
  96.     }  
  97.   
  98. }  

代码很简单,包括使用反射、读取系统属性、创建socket、读写文件的操作,把编译后的class放至C:/tmp/com/test目录下,然后在C:/tmp目录用命令行下执行java com.test.PolicyTest,可以看到,操作都可以成功:

自定义java.policy配置(如何让你的类禁止读写文件?禁止创建Socket对象?)

但如果执行java -Djava.security.manager com.test.PolicyTest,你会看到以下结果:

自定义java.policy配置(如何让你的类禁止读写文件?禁止创建Socket对象?)

很明显,由于我们启动了安全检查,这里的操作全部失败。现在,到了编写自己的policy文件的时候了,我取名为my.policy,内容如下: 

[plain] view plain copy
  1. grant codeBase "file:/C:/tmp/*" {  
  2.     permission java.security.AllPermission;  
  3. };  

我把这个文件也放在C:/tmp下,然后执行
java -Djava.security.manager -Djava.security.policy=my.policy com.test.PolicyTest

自定义java.policy配置(如何让你的类禁止读写文件?禁止创建Socket对象?)

由于我分配的是AllPermission,所以全部操作都能成功,但这不是我想要的。我希望使用反射和读取系统属性能成功,而创建socket和读写文件要失败,于是更改my.policy:

[plain] view plain copy
  1. grant codeBase "file:/C:/tmp/*" {  
  2.     permission java.lang.reflect.ReflectPermission "suppressAccessChecks";  
  3.     permission java.lang.RuntimePermission "accessDeclaredMembers";  
  4.     permission java.util.PropertyPermission "*", " read,write";  
  5. };  

然后再次执行java -Djava.security.manager -Djava.security.policy=my.policy com.test.PolicyTest

自定义java.policy配置(如何让你的类禁止读写文件?禁止创建Socket对象?)

达到理想效果了,反射和系统属性读取没有问题,socket创建及文件读写被禁止。

4.通过上面的PolicyTest示例,得出解决我的问题的方案是,把服务器的class和客户端class分别放在不同的classpath 下,以windows为例(如果是在linux上,只需要更改对应的路径即可),假设服务器的class文件放在C:/classes/local下,而客户端上传的class放在C:/classes/remote下,my.policy放在C:/classes下,内容为:

[plain] view plain copy
  1. grant codeBase "file:/C:/classes/local/*" {  
  2.     permission java.security.AllPermission;  
  3. };  
  4. grant codeBase "file:/C:/classes/remote/*" {  
  5.     permission java.lang.reflect.ReflectPermission "suppressAccessChecks";  
  6.     permission java.lang.RuntimePermission "accessDeclaredMembers";  
  7.     permission java.util.PropertyPermission "*", " read,write";  
  8. };  

对应的启动脚本为java -Djava.security.manager -Djava.security.policy=C:/classes/my.policy -classpath C:/classes/local;C:/classes/remote xx.MainClass注意:如果是linux,除了更改对应路径外,还需要把classpath中的;号改为:号。

5.大功告成!你可能会问,我怎么知道有哪些权限可以配置,如何配置呢?除了看官方的文档说明外,还有一个小技巧。就是实现自己的SecurityManager,然后覆盖checkPermission(Permission perm)方法,如下:

[java] view plain copy
  1. package com.test;  
  2. import java.security.Permission;  
  3. public class MySecurityManager extends SecurityManager {  
  4.  public void checkPermission(Permission perm) {  
  5.   System.out.println("perm=" + perm);  
  6.   super.checkPermission(perm);  
  7.  }  
  8. }  

为了让MySecurityManager有效,你可以直接在代码中通过System.setSecurityManager(new MySecurityManager());设置,或者在JVM参数中设置-Djava.security.manager=com.test.MySecurityManager,这两种方式是一样的,运行方式还是一样:java -Djava.security.manager=com.test.MySecurityManager -Djava.security.policy=my.policy com.test.PolicyTest

自定义java.policy配置(如何让你的类禁止读写文件?禁止创建Socket对象?)

内容太多,未显示全,不过可以看到,你需要获取的权限都被打印了出来。


转载:https://blog.csdn.net/zhoche2008/article/details/7101830